生成器
generator。它保存的是列表元素生成的算法,每调用一次next()函数时,就会根据算法返回一个元素,避免了一次性生成过多的元素,导致内存溢出。
定义
在for循环中,可以使用如下方法生成一个列表:
def f(m):
return m*2
L = [f(m) for m in 'abc']
print(L)
将定义列表的[]换成()后,得到的L就是一个generator。
函数
可以将一个普通的函数定义成一个generator,只需要函数中使用yield。
在执行函数时,执行到yield语句时都会返回一个值,并停止函数的执行,下一次执行该函数时,会从上一次yield语句的下一条开始执行,直到下一个yield语句或函数结束。如:
def f(m):
for k in range(m):
print(k)
yield "k=%s" % k
print('k -----%s' % k)
多次调用该函数,输出结果为:第一次调用时,首先输出0,这是函数中第三行输出的,其次输出的内容为调用该函数时print()语句输出的,要注意的是并没有直接输出函数中第二个输出语句,也就是说函数在走到yield处时已经停止。
再一次调用该函数时,首先输出的是k ----0,这是上一次调用该函数后剩余的部分,其次才输出本次函数中应该输出的部分。同样,也没有执行yield后的部分,直到第三次调用时才会执行。
从这可以看出generator的执行顺序为:遇yield语句则本次执行结束,并将yield值返回;下一次再执行该函数时,会从yield下一条语句开始,直到函数结束或遇到下一条yield。
遍历
通过next()
def f(m):
return m*2
L = (f(m) for m in 'abc')
print(next(L)) # aa
print(next(L)) # bb
print(next(L)) # cc
由于L中只有三个元素,如果再调用next(L)的话,就会报错:StopIteration
for循环
def f(m):
return m*2
L = (f(m) for m in 'abc')
for k in L:
print(k)
一般来说,获取生成器元素都是通过for循环完成,因为它可以避免调用过多next导致报错。示例
通过generator生成一个杨辉三角:
def f(m):
l = [1]
n = 0
while n < m:
yield l
l = [sum(i) for i in zip([0] + l, l + [0])]
n += 1
o = f(10) # 输出前10行
for k in o: # o是generator,通过循环输出
print(k)
[0]+l会将l所有元素往后移动一位,再通过zip()函数,这样会使得l的第一位与0结合着一个元组,l的第n个元素会和第n-1个元素结合——zip()的方法的效果。再通过sum()函数,将zip()中各个元素(是元组)求和,就得到了下一行的数据。
迭代器
那些直接作用于for循环的对象通称为可迭代对象:Iterable。
generator除了可以用于for循环还可以用于next()函数,这样可以被next()函数调用并不断返回下一个值的对象叫做迭代器:Iterator。它代表着一个可迭代的数据流,每一次通过next()方法得到下一个数据。
注意:list,tuple,dict,str等是Iterable,但不是Iterator。可以通过iter()函数将它们变成Iterator。
装饰器
基础
设计模式中有装饰模式,装饰器与它的功能完全一致:动态地给对象(python中指函数)添加功能。比如:
def now(year, month, day):
print("%d-%d-%d" % (year, month, day))
def outer(func, *args, **kwargs):
print("先执行")
func(*args, **kwargs) # 执行传入的函数
print("后执行")
outer(now, 2016, 10, 9)
想为now函数执行前后添加一些功能,可以将函数当作参数传入另一个函数中,在执行前后分别执行另外一些功能。这种写法是完全可行的。
但这种方法有一个弊端:下一次想往now中输入不同的参数必须重新调用outer(now,args),因此可以将outer的返回值定义成一个函数,执行该函数首先会执行now前面语句,再执行now,最后执行now后面语句。可以改写成:
def now(year,month,day):
print('%d-%d-%d'%(year,month,day))
def decorator(func):
def wrap(*args,**kwargs): # 接收任意参数
print('before---') # 执行装饰过程
func(*args,**kwargs) # 并将任意参数给传递到被装饰的函数中
print('after----') # 执行装饰过程
return wrap
o = decorator(now) # 返回一个函数
o(1111,22,33) # 可以多次使用返回的函数,且可以传入任意的参数
o(2222,33,44)
在python中,可以通过@语句将上面的代码进行简写。如:def decorator(func):
def wrap(*args,**kwargs):
print('before---')
func(*args,**kwargs)
print('after----')
return wrap
@decorator
def now(year,month,day):
print('%d-%d-%d'%(year,month,day))
now(1111,22,33) # 可以多次使用返回的函数,且可以传入任意的参数
now(2222,33,44)
在被装饰的方法上使用@语句,@后跟的是装饰方法(即本例中的decorator),也就是说@语句只是对下面的简写:
now = decorator(now)
它将now重新赋值为decorator的返回值,再执行now方法时,就是decorator的返回值,因此就会将原来的now装饰过。叠加
装饰器函数可以将被传入到另一个装饰器函数中,使用@语句时,@语句一样可以叠加,但要注意叠加的顺序。如:@decorator2
@decorator1
def now(year,month,day):
print('%d-%d-%d'%(year,month,day))
它相当于now = decorator2(decorator1(now)),如果将两个@语句调换顺序,那就是now = decorator1(decorator2(now))。也就是说:谁在上,谁就是最外层的装饰者。
传参
也可以为装饰器函数传参,但不能直接把参数写在装饰器里面。如下:
def outer(func, out):
def wrap(*args, **kwargs):
print("先执行"+out)
func(*args, **kwargs)
print("后执行")
return wrap
@outer('------')
def now(year, month, day):
print("%d-%d-%d" % (year, month, day))
在运行时,会报错:TypeError: outer() missing 1 required positional argument: 'out'。这是因为out本身需要两个参数,而@语句中只传了一个。当然,如果不使用@语句,上面的代码是可以的:
now = outer(now, '-------')
如果想使用@语句,需要将上面函数进行改写。def outer(out): # 使用一个函数存储传入装饰函数需要的参数
def second(func): # 原来的装饰类
def wrap(*args,**kwargs):
print("先执行"+out)
func(*args, **kwargs)
print("后执行")
return wrap
return second
@outer('******************')
def now(year, month, day):
print("%d-%d-%d" % (year, month, day))
也可以将outer()定义多个参数,只需要在使用@语句时传入对应个数的参数即可。
总结:在不方便传参时,可以使用函数对已有函数进行包装,并将参数传入外层函数,在内层函数中直接外层函数的参数。
上面的@语句是相当于对下面的的简写:now = outer('******************')(now)
首先outer('******************')返回的是second函数,并且参数out为传入的一系列*号。所以(now)相当于second(now),返回的是wrap函数,同时func为now。
该语句执行完毕后,now就是wrap函数,再使用now执行时就相当于执行wrap函数。
__name__属性
使用@语句时,会将原函数给重新赋值(看@语句的非简写格式),所以它的__name__属性值也发生了变化。如:
def outer(out):
def second(func):
def wrap(*args, **kwargs):
print("先执行"+out)
func(*args, **kwargs)
print("后执行", func.__name__)
return wrap
return second
@outer('******************')
def now(year, month, day):
print("%d-%d-%d" % (year, month, day))
print(now.__name__) # wrap
通过上面的分析可以知道@语句之后,now指向的就是wrap函数,所以now.__name__输出wrap是完全正常的。但这也导致了一些使用__name__属性的地方发生了错乱。解决办法是使用functools模块下的wrap。如下:
import functools
def outer(out):
def second(func):
@functools.wraps(func) # 在最深层的装饰函数上使用@语句。
def wrap(*args, **kwargs):
print("先执行"+out)
func(*args, **kwargs)
print("后执行", func.__name__) # now
return wrap
return second
@outer('******************')
def now(year, month, day):
print("%d-%d-%d" % (year, month, day))
print(now.__name__) # now
对于在wrap中使用func.__name__属性,返回的肯定是now,因为func指向的就是now函数。
使用functools.wrap后,再执行now.__name__,返回的就是now。这和没有使用装饰器前完全一样。
总结
有参数的装饰器函数:
def outer(out):
def second(func):
@functools.wraps(func) # 在最深层的装饰函数上使用@语句。
def wrap(*args, **kwargs):
print("先执行"+out)
func(*args, **kwargs)
print("后执行", func.__name__) # now
return wrap
return second
@outer('******************')
def now(year, month, day):
print("%d-%d-%d" % (year, month, day))
不含参数的装饰器:
def outer(func):
@functools.wraps(func)
def wrap(*args, **kwargs):
print("before-------")
func(*args, **kwargs)
print('after**********')
return wrap
使用
@outer # 有参数时写成@outer(参数)
def now(year, month, day):
print("%d-%d-%d" % (year, month, day))
now(111,222,333) # 像正常函数一样使用被装饰的函数
偏函数
将一个函数的某些参数固定住,并返回一个新的函数,这个返回的函数就是偏函数。使用functools.partial。偏函数有助于简化对某些函数的调用。如
def now(year, month, day):
print("%d-%d-%d" % (year, month, day))
p = functools.partial(now, year=2016) # 生成一个偏函数,并将year参数固定成2016
p(month=11, day=22) # 2016-11-22
p(year=2022,month=22,day=44) # 2022-22-44 并且重新定义了偏函数中year的值
在调用p函数时,只是传入了month,day,这是因为year在定义p函数时已经传入了。当然也可以为year传入另一个值。
创建偏函数时传入的参数和调用偏函数时传入的参数合在一起就是原函数的参数,并且同样的参数形式时偏函数的参数要排在前面。如:
def now(year, month, day):
print("%d-%d-%d" % (year, month, day))
p = functools.partial(now, 2016) # 生成一个偏函数,并将year参数固定成2016
p(2022, 22) # 2016-2022-22
可以看出 ,2016,2022,22会依次传入到now的三个形参中。由于2016,2022,22三个参数都是位置参数,所以定义偏函数时的2016排在最前面。如果定义偏函数时,使用了关键字参数,那么在调用偏函数时一定要注意参数问题。始终要记住:定义参数与传参时,顺序为位置参数、默认参数、可变参数、自定义关键参数、关键字参数。如:
def now(year, month, day):
print("%d-%d-%d" % (year, month, day))
p = functools.partial(now, month=2016)
p(2022, day=22) # 2022-2016-22
首先2022是位置参数,所以排在最前面;如果跟两关键字参数month与day。如果不写成day=22,则会报错“now() got multiple values for argument 'month'”——这是因为22也是位置参数,也排在month=2016前面,所以对于month就有两个值:一个是定义偏函数时传入的month=2016,一个是调用时传入的22。