1. 迭代器
Python的迭代器和生成器是一个非常好的能够构建数据管道的方式,试想一下,如果全部的数据都一股脑的读入内存,那么当数据量极大的时候内存绝对会爆掉的,这个时候迭代器和生成器的优势就极大地体现出来了,因为迭代器和生成器一个函数,每次都利用函数记住取到了哪个位置的数据,下次取得时候直接从上次取出的地方再取数据即可。
构建迭代器时最重要的就是需要重写两个方法,__iter__
和 __next__
。StopIteration
异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration
异常来结束迭代。下面我构建一个迭代器当作示例:
class MyNumbers:
def __init__(self, list):
# 获取传入的列表
self.list = list
# 当前的索引
self.index = 0
# 列表的长度,即索引的界限
self.n = len(list)
def __iter__(self):
# 返回迭代器本身
return self
def __next__(self):
if self.index < self.n:
num = self.list[self.index]
self.index += 1
return num
else:
raise StopIteration
itera_list = MyNumbers([1,4,6,9])
for i in itera_list:
print(i)
以上迭代器输出为:
1
4
6
9
2. 生成器
生成器与迭代器其实差不多,可以理解为生成器是迭代器的子类,是用 yield
关键字来定义的。一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。
虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
下面我来示例一下用生成器生成一个1-10数字的平方的函数:
def gengerate():
for i in range(1, 11):
yield i **2
for i in gengerate():
print(i)
可以看到,生成器本质上就是一个迭代器的再封装。
3. 装饰器
在了解装饰器前,我们先来讲一讲Python中的闭包,因为装饰器本来就是闭包的一个应用。
3.1 闭包
闭包的概念如下:
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。
简单来说,闭包就是外部函数中定义一个内部函数,内部函数引用外部函数中的变量,外部函数的返回值是内部函数,格式如下:
def fun1(arg1):
def fun2(arg2):
# some function with arg1
return value
return fun2
闭包有以下的特征:
- 嵌套函数
- 内层函数引用了外层函数的变量
- 内层函数作为返回值返回给外层函数
接下来我们使用一个加法函数来实现一个简单的闭包进行示例。
def fun1(arg1):
def fun2(arg2):
return arg1 + arg2
return fun2
add_3 = fun1(3)
r1 = add_3(10)
r2 = add_3(12)
print(r1) # 13
print(r2) # 15
执行结果为 r1=13,r2=15
,可以看到,其中的 add_3
相当于就是内层函数 fun2
的一个实例化,使用的时候直接往实例化后的函数传入值即可得到最后的结果。
从上面的结果可以看到,单纯的闭包使用其实作用以及必要性都不大,最多看上去有点帅而已,但是,没必要。但是,接下来我们要将的装饰器就是闭包的一个应用,能够简化很多的代码。
3.2 python参数传递
在讲装饰器之前再介绍一个,Python中的参数传递。
很多情况下我们写的代码都是针对的知道有多少个参数的情况下,但是,还是有一些情况,即不知道参数有多少的情况会出现,这种情况我们一般采用的是数组的方式来进行参数的传递,但是,Python中有更为简单的方式来解决这一类的参数传递问题。
-
打包成元组
当你传入的参数个数不知道多少的时候,可以将参数一股脑的全部都塞到函数里面去,然后在函数里面使用*args
来进行接收,这样,Python会自动将传递的参数全部打包到元组args
中,想要得到参数只需要访问元组就行,示例如下:def add(*args): for i in args: print(i) add(1, 2, 3, 4, 5, 6, 9, 78)
该示例会将传入的参数
1, 2, 3, 4, 5, 6, 9, 78
挨个打印。 -
打包成字典
当你想不知道多少个参数,且想要为每一个参数命名时,可以使用这种类型的参数传递,传递参数时使用head='round'
形式,接收时用**kwargs
进行接收。def add(**kwargs): for key, value in kwargs.items(): print('{}={}'.format(key, value)) add(one=12, tow='amgds', three='drogon')
-
综合传递
元组传参和字典传参已经能够满足大部分传参场景。综合传参指的是这两种的组合,在很多源码中我们都经常看到这样的组合。def add(*args, **kwargs): for i in args: print(i) for key, value in kwargs.items(): print('{}={}'.format(key, value)) add(1, 2, 3, 4, 5, 6, 9, 78, one=12, tow='amgds', three='drogon')
有人问万一是
add(1,2,one=12,5,6,tow='amgds')
这种结构Python怎么解决呢。这个问题大可不必担心,会报错的。因为Python规定了如果写了这种ne=12
字典传参的形式,那么这种形式必须放在最后,即后面如果还有参数那也必须是这种形式,否则会报错。
3.3 一般的装饰器
装饰器有点类似于JAVA中的注解,主要用于插入日志、性能测试、事务处理、缓存、权限校验等场景。
首先我们来看看一般的装饰器是怎么写的,下面我们写一个统计运行时间的装饰器。
import time
def fun1(func):
def fun2(*args, **kwargs):
t1 = time.time()
func(*args, **kwargs)
t2 = time.time()
print(t2-t1)
return fun2
@fun1
def add(a, b):
time.sleep(5)
return a + b
if __name__ == '__main__':
add(1,2)
其中,上面的 @fun1
就相当于 add=fun1(add)
,如果看了上面的闭包,是不会想到这就是闭包的样式,所以,装饰器其实就是闭包的一种使用。这里的 @fun1
实际是将整个函数作为参数传进去,再将其返回而已。
3.4 带参数的装饰器
既然装饰器相当于java中的注解,那么装饰器能不能像注解 @RequestMapping("/hello2")
一样传值呢?
其实是可以的,虽然装饰器只能接收一个参数,并且还是函数类型,但是这种实现的话需要在装饰器外面再进行一次函数嵌套,即三层函数嵌套。示例代码如下:
def operate(flag):
def decorator(fn):
def inner(num1, num2):
if flag == "+":
print("加法结果如下")
elif flag == "-":
print("减法结果如下")
result = fn(num1, num2)
return result
return inner
# 返回装饰器
return decorator
# 使用装饰器装饰函数
@operate("+")
def add(a, b):
result = a + b
return result
print(add(1, 3))
这种实现其实有些偏麻烦了,下节讲了类装饰器后这种形式其实可以改为类装饰器的形式。
3.5 类装饰器
类装饰器,顾名思义,就是一个将装饰器写成一个类的形式。我们来看下面的例子进行讲解。
import time
class operate():
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
start = time.time()
result = self.func(*args, **kwargs)
end = time.time()
print('执行用时: {}'.format(end - start))
return result
@operate
def add(a, b):
time.sleep(2)
result = a + b
return result
print(add(1, 3))
先别急,我们来仔细看看代码的。
上述装饰器类的装饰代码 @operate
实际上就相当于 add=operate(add)
相当于就是将类 operate
实例化了,实例化后,问题就来了,既然 add
已经成为了一个类的实例化,那么我们肯定是不能使用 add(1,3)
来运行一个实例化的,但是我们却需要使用 add(1,3)
这种形式来运行怎么办呢?
这种情况就需要使用 __call__
方法了。只要在创建类型的时候定义了 __call__
方法,这个类型就是可调用的,即使用 add(1,3)
调用实例化后,实例化的参数会传递到 __call__
方法中,然后运行 __call__
方法中的内容。
3.6 带参数的类装饰器
前面我讲过,可以用类装饰器实现带参数的装饰器。事实上,当遇到装饰器看不懂时,我们不妨将其拆开来看,即拆解成为其等价的形式。带参数的类装饰也是如此,假如我们实现的是例子与前面3.4节的例子一样,在函数 add
前面写一个类装饰器 @operate('+')
,这不就等价于 add=operate('+')(add)
吗,这样一来,思路瞬间就清晰了。
class operate():
def __init__(self, operation):
self.operation = operation
def __call__(self, func):
def inner(*args, **kwargs):
if self.operation == '+':
print('正在运行加法')
elif self.operation == '-':
print('正在运行减法')
result = func(*args, **kwargs)
return result
return inner
@operate("+")
def add(a, b):
result = a + b
return result