python生成器
生成器(generator)指代的是生成器对象,它可以由生成器表达式得到,也可以使用yield关键字得到一个生成器函数,调用这个生成器函数就可以得到一个生成器对象。另外,通过数据的转换也可以获取生成器。
生成器对象是一个可迭代对象,它是一个迭代器。生成器的作用是延迟计算,惰性求值。
生成器表达式产生生成器对象
#生成器表达式产生生成器 a =(x**2 for x in range(5)) print(type(a)) print(next(a)) print(next(a)) print(next(a)) print(next(a)) 结果为: <class 'generator'> 0 1 4 9
生成器函数
生成器函数也就是函数体中包含yield关键字的函数,这个函数会返回一个生成器对象!
def inc(): for i in range(5): yield i print(type(inc)) print(type(inc())) x=inc() print(type(x)) print(next(x)) for m in x: print(m,"**") for i in x:#因为x为生成器对象,所以它只能被遍历一次。再次遍历不会被执行了。 print(i,"*") 结果为: <class 'function'> <class 'generator'> <class 'generator'> 0 1 ** 2 ** 3 ** 4 **
由上面的例子可以发现,普通的函数调用,函数会立即执行完毕,但是生成器函数不会,它可以使用next函数执行多次,直到最后。
def func(): print("111") yield 222 gener = func() ret = gener.__next__() #print(ret) 结果为: 111
而生成器函数等价于生成器表达式,只不过生成器函数可以更加的复杂。比如上面的生成器函数可以写成下面的生成器表达式。
y = (i for i in range(5)) print(type(y)) print(next(y)) print(next(y)) 结果为: <class 'generator'> 0 1
在生成器函数中,可以使用多个yield语句,执行一次后会暂停执行,然后把yield表达式的值返回。函数如果再次执行的话,会执行到下一个yield语句,在生成器函数中, return依然可以终止程序的运行,单return语句的返回值不能被获取到。
在生成器函数中,return会导致无法继续获得下一个值,这个时候会抛出stopiteration异常,而如果函数没有显示的return语句,如果生成器函数执行到结尾,一样会抛出stopiteration异常。
def gen(): print("line 1 ") yield 1 print("line 2") yield 2 print("line 3") return 3 next(gen())#line 1 next(gen())#line 1都是line1因为这不是同一个生成器对象,第二次执行函数,又返回了一个生成器对象。 g =gen() print(next(g)) print(next(g)) print(next(g)) 结果为: line 1 line 1 line 1 1 line 2 2 line 3 --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-16-3c0007202311> in <module> 13 print(next(g)) 14 print(next(g)) ---> 15 print(next(g)) StopIteration: 3 def gen(): print("line 1 ") yield 1 print("line 2") yield 2 print("line 3") return 3 next(gen()) next(gen()) g =gen() print(next(g)) print(next(g)) print(next(g, 'End')) #没有元素了,给个缺省值 print(next(g, 'End')) 结果为: line 1 line 1 line 1 1 line 2 2 line 3 End End
所以,生成器函数就是包含yield语句的函数,而生成器函数生成“生成器”的时候,生成器函数的函数体并不会立即执行,它需要next函数,有点拨一下转一下的意思,next(generator)会从函数的当前位置向后执行到碰到的第一个yield语句,这个时候有值会弹出值,并且暂停函数的执行。
再次调用next函数,重复上面的步骤。,而如果没有多余的yield语句被执行,如果再次调用next函数,就会抛出stopiteration异常。
生成器应用
现在有一个需求,比如我像一个服装厂订购了100件衣服,这个时候服装厂全部给我生成好了。比如下面这个代码。
def cloth(): lst = [] for i in range(10): lst.append("cloth"+str(i)) return lst cloth() 结果为: ['cloth0', 'cloth1', 'cloth2', 'cloth3', 'cloth4', 'cloth5', 'cloth6', 'cloth7', 'cloth8', 'cloth9']
但是,现在我还没有那么多的学习,一次性给我那么多,我根本没法用。所以,最好的我要一件,你给我一件。这个时候就需要用到生成器了。
def cloth(): lst = [] for i in range(10): yield "cloth"+str(i) a = cloth() print(a.__next__()) print(a.__next__()) print(a.__next__()) 结果为: cloth0 cloth1 cloth2
所以,上面的第一种就是一次性全部拿出来,会占用内存,第二种生成器,一次就一个,用多少生成多少,用next函数一个一个的拿,然后指向下一个。
生成器还可以用在诸如无限循环,计算器,处理递归等问题上,应用最广泛的是协程。
#生成器处理无限循环,就不会陷入死循环,它拨一下,转一下。 def counter(): i = 0 while True: i+=1 yield i def inc(a): return next(a) c = counter() print(inc(c)) print(inc(c)) for i in range(10): print(inc(c)) 结果为: 1 2 3 4 5 6 7 8 9 10 11 12
应该注意的是,如果上面的代码写成下面这样,每次就会产生一个新的“生成器”,达不到拨一下转一下的效果。
def counter(): i = 0 while True: i+=1 yield i def inc(): a = counter() return next(a) print(inc()) print(inc()) print(inc()) 结果为: 1 1 1
下面用生成器配合无限循环写一个计数器。
def inc(): def counter(): i = 0 while True: i+=2 yield i c = counter() return lambda :next(c) foo = inc() print(foo()) print(foo()) 结果为: 2 4
这个例子和上面无限循环差不多,都是一个意思,只是换了一种写法,同时这个例子用到了lambda表达式,而return返回的是一个匿名函数。它也等价于下面的代码:
def inc(): def counter(): i = 0 while True: i+=2 yield i c = counter() def _inct(): return next(c) return _inct foo = inc() print(foo()) print(foo()) print(foo())
生成器同样可以用来处理递归的问题,比如下面这个例子实现斐波拉契数列。可以想生成多少就生成多少。
def fib(): x = 0 y = 1 while True: yield y x,y=y,x+y foo = fib() for _ in range(5):#生成5个 print(next(foo)) for _ in range(5):#再生成5个 print(next(foo)) 结果为: 1 1 2 3 5 8 13 21 34 55
上面的这段代码等价于下面的这段代码。
pre = 0 cur = 1 print(pre,cur,end=" ") def fib1(n,pre = 0,cur=1): pre,cur=cur,pre+cur print(cur,end=" ") if n ==2: return fib1(n-1,pre,cur) fib1(11) 结果为: 0 1 1 2 3 5 8 13 21 34 55 89
生成器还有一个高级用法,那就是协程coroutine,协程比进程、线程轻量级,它是在用户空间调度函数的一种实现,在Python 3 中,asyncio就是协程实现,已经加入到标准库,同时在Python3.5中,使用async\await关键字直接原生支持协程。
协程调度器实现原理为:有两个生成器A,B,next(a)后,A执行到了yield语句暂停,然后去执行next(B),B执行到yield语句也暂停,然后再次调用next(A),再调用next(B),周而复始,就实现了调度的效果。
同时应该注意,协程是一种非抢占式的调度。
yield from
yield fromީ是Python3.3出现的新语法,yield from iteratle 是 for item in iterable:yield item形式的语法糖。
def inc(): for i in range(1000): yield i foo = inc() print(next(foo)) print(next(foo)) print(next(foo)) def inc(): yield from range(1000) foo = inc() print(next(foo)) print(next(foo)) print(next(foo)) 结果都为: 0 1 2
上面的两端代码其实是等效的。
再比如下面这个例子,从一个可迭代对象中一个个拿元素。
def counter(n): for x in range(n): yield x def inc(n): yield from counter(n) foo = inc(10) print(next(foo)) print(next(foo)) print(next(foo)) 结果为: 0 1 2