大家好,小编来为大家解答以下问题,python生成器有几种写法,python生成器函数例子,今天让我们一起来看看吧!
本文部分参考:Python迭代器,生成器–精华中的精华 https://www.cnblogs.com/deeper/p/7565571.html
一 迭代器和可迭代对象
迭代器是访问集合元素的一种方式。火车头采集器AI伪原创。迭代器只能往前不会后退。迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素,仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件。
特点:a)访问者不需要关心迭代器内部的结构,仅需通过next()方法或不断去取下一个内容
b)不能随机访问集合中的某个值 ,只能从头到尾依次访问
c)访问到一半时不能往回退
d)便于循环比较大的数据集合,节省内存
e)也不能复制一个迭代器。如果要再次(或者同时)迭代同一个对象,只能去创建另一个迭代器对象。
enumerate()的返回值就是一个迭代器,我们以enumerate为例:
a = enumerate(['a','b'])
for i in range(2): #迭代两次enumerate对象
for x, y in a:
print(x,y)
print(''.center(50,'-'))
结果:
0 a
1 b
-----------------------分割线------------------------
-----------------------分割线------------------------
可以看到再次迭代enumerate对象时,没有返回值。
我们可以用linux的文件处理命令vim和cat来理解一下:
a) 读取很大的文件时,vim需要很久,cat是毫秒级;因为vim是一次性把文件全部加载到内存中读取;而cat是加载一行显示一行
b) vim读写文件时可以前进,后退,可以跳转到任意一行;而cat只能向下翻页,不能倒退,不能直接跳转到文件的某一页(因为读取的时候这个“某一页“可能还没有加载到内存中)。
正式进入python迭代器之前,我们先要区分两个容易混淆的概念:可迭代对象(Iterable)和迭代器(Iterator)。
1.1可迭代对象
定义:迭代器是一个对象,不是一个函数。只要它定义了可以返回一个迭代器的__iter__方法,或者定义了可以支持下标索引的__getitem__方法,那么它就是一个可迭代对象。
注意: 集合数据类型,如list、tuple、dict、set、str都是可迭代对象Iterable,却不是迭代器Iterator。
如何判断一个对象是可迭代对象呢?可以通过collections模块的Iterable类型判断:
from collections import Iterable
print(isinstance([], Iterable))
print(isinstance({}, Iterable))
print(isinstance('abc', Iterable))
print(isinstance({'name':'join','age':23},Iterable))
print(isinstance(set([2,3]),Iterable))
结果为:
True
True
True
True
True
再看:
from collections import Iterator
print(isinstance([], Iterator))
print(isinstance({}, Iterator))
print(isinstance('abc', Iterator))
print(isinstance({'name':'join','age':23},Iterator))
print(isinstance(set([2,3]),Iterator))
结果为:
False
False
False
False
False
1.2迭代器
定义:任何实现了__iter__()和__next__()(python2中实现next())方法的对象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一个值。
这里我们来看一下迭代器和可迭代对象的使用方法。
问题:不适用for,while以及下表索引,怎么遍历一个list?
l1=[2,3,5]
iter_l1 = l1.__iter__()
# 或者iter(iter_l1)
# iter(iter_l1)是python的内置函数,
# l1.__iter__()调用的是l1对象的__iter__()方法。
# 下面的next()函数和__iter__()函数类似。
print(next(iter_l1)) # 或print(iter_l1.__next__()
print(next(iter_l1))
print(next(iter_l1))
print(next(iter_l1))
结果为:
2
3
5
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-126-eb58865cb644> in <module>
4 print(next(iter_l1))
5 print(next(iter_l1))
----> 6 print(next(iter_l1))
结论:思路就是,先得到当前对象(当前对象是可迭代对象)的迭代器,然后每次执行next(iter_l1)就可以得到当前对象的一个值。
修改一下:
l1=[2,3,5]
iter_l1 = l1.__iter__()
for i in iter_l1:
print(i,end=',')
结果为:
2,3,5,
再次执行for语句如下:
for i in iter_l1:
print(i,end=',')
此次返回结果为空,可以看到迭代器只能遍历一次对象,不能重复遍历。由于for语句可以自动处理StopIteration异常,所以这里没有报出StopIteration,而是没有任何结果。
看一个例子。我们想得到Fibonacci数列,思路是定义一个可迭代对象类Fib;然后在定义一个迭代器类FibIterator,使用方式是:
① 实例化一个可迭代对象: fib = Fib()
② 得到fib的一个迭代器: fib_iter = iter(fib)
③ 然后每次调用next(fib_iter)可得到fibonacci数列中的一个数。
class FibIterator():
'''
定义迭代器类
'''
def __init__(self,num,a,b,current):
self.num = num
self.a = a
self.b = b
self.current = current
def __iter__(self):
return self
def __next__(self):
if(self.num-1>=0):
self.num = self.num-1
self.current = self.a
self.a = self.b
self.b = self.b+self.current #以上两步赋值操作可省略中间变量直接写为self.a,self.b = self.b,self.a+self,b
return self.current
else: raise StopIteration
class Fib:
'''
定义可迭代对象所属类
'''
def __init__(self,num): #num表示该数列的长度
self.a = 1
self.b = 2
self.current=self.a
self.num = num
def __iter__(self):
return FibIterator(self.num,self.a,self.b,self.current)
fib = Fib(20)
fib_iter = iter(fib)
for i in range(20):
print(next(fib_iter),end=',')
结果为:
1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,
这里定义的Fibonacci类的__init__函数有一个参数num,表示一共得到几个fibonacci数。
由于可迭代对象是实现了__iter__()方法的,迭代器对象是实现了__iter__()和__next__()方法的,能否只定义一个迭代器类呢?
试一下。
class Fib:
def __init__(self,num):
self.num = num
self.a = 1
self.b = 2
self.current = self.a
def __iter__(self):
return self
def __next__(self):
if(self.num-1>=0):
self.num = self.num-1
self.current = self.a
self.a = self.b
self.b = self.b+self.current #以上两步赋值操作可省略中间变量直接写为self.a,self.b = self.b,self.a+self,b
return self.current
else: raise StopIteration
fib = Fib(10)
for i in fib:
print(i,end=',')
结果为:
1,2,3,5,8,13,21,34,55,89,
当我们再次执行for语句时,
for i in fib:
print(i,end=',')
同上面的分析一样,结果为空。如果想再次得到fibonacci数列的前10个数,就必须重新实例化Fib对象。
结论:为什么不只保留Iterator的接口而还需要设计Iterable呢?
因为迭代器迭代一次以后就空了,那么如果list,dict也是一个迭代器,迭代一次就不能再继续被迭代了,这显然是反人类的;所以通过__iter__每次返回一个独立的迭代器,就可以保证不同的迭代过程不会互相影响。
另外,迭代器是惰性的,只有在需要返回下一个数据时它才会计算。所以,Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
下面的例子得到全体自然数(下面我们用生成器可以得到更简单的写法)。
class Natural:
def __init__(self):
pass
def __iter__(self):
return NaturalIterator()
class NaturalIterator:
def __init__(self):
self.beg=0
self.current=self.beg
def __iter__(self):
return self
def __next__(self):
self.current += 1
return self.current
# 显示前20个自然数
n1=Natural()
n1_iter = iter(n1)
for i in range(20):
print(next(n1_iter),end=',')
结果为:
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
作业:使用迭代器和可迭代对象实现得到全体fibonacci数。
二 生成器
2.1 生成器的定义和使用
定义:生成器其实是一种特殊的迭代器,它不需要再像上面的类一样写__iter__()和__next__()方法了,只需要一个yiled关键字。
生成器一定是迭代器(反之不成立)。
Python有两种不同的方式提供生成器:
- 生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。 yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
- 生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象, 而不是一次构建一个结果列表
我们先用一个例子说明一下:
def generate_even():
i=0
while True:
if i%2==0:
yield i
i += 1
g=generate_even()
dir(g) # 可以看到里面有__iter__()方法和__next__()方法,所以生成器也是迭代器。
for i in range(10):
print(g.__next__(),end=',')
结果为:
0,2,4,6,8,10,12,14,16,18,
现在解释一下上面的代码:
我们知道,一个函数只能返回一次,即return以后,这次函数调用就结束了;
但是生成器函数可以暂停执行,并且通过yield返回一个中间值,当生成器对象的__next__()方法再次被调用的时候,生成器函数可以从上一次暂停的地方继续执行,直到下一次遇到yield语句(此时会返回yield后面的值,如果有的话)或者触发一个StopIteration。
了解协同程序:
a) 生成器的另外一个方面是协同程序的概念。协同程序是可以运行的独立函数调用,可以暂停或者挂起,并从程序离开的地方继续或者重新开始。
b) 可以在调用者和被调用的之间协同程序通信。
c) 在程序暂停时可以传参:举例来说,当协同程序暂停时,我们仍可以从其中获得一个中间的返回值,当调用回到程序中时,能够传入额外或者改变了的参数,但是仍然能够从我们上次离开的地方继续,并且所有状态完整。
生成器表达式类似于列表推导式,只是把[]换成(),这样就创建了一个生成器。
gen = (x for x in range(10))
下面我们用生成器来实现前面的fibonacci数列和全体自然数。
# 生成前n个fibonacci数
def fib(n):
a, b = 0, 1
count=0
while True:
if(count>n):
break
count += 1
yield b
a, b = b, a+b
f = fib(20)
for item in f:
print(item,end=',')
结果为:
1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,
def Natural():
n=0
while True:
yield n
n += 1
g_n1=Natural()
for i in range(20):
print(next(g_n1),end=',')
结果为:
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,
2.2 send方法
生成器函数最大的特点是可以接受外部传入的一个变量,并根据变量内容计算结果后返回。这是生成器函数最难理解的地方,也是最重要的地方,协程的实现就全靠它了。
看一个小猫吃鱼的例子:
def cat():
print('我是一只hello kitty')
while True:
food = yield
if food == '鱼肉':
yield '好开心'
else:
yield '不开心,人家要吃鱼肉啦'
中间有个赋值语句food = yield,可以通过send方法来传参数给food,试一下:
情况1)
miao = cat() #只是用于返回一个生成器对象,cat函数不会执行
print(''.center(50,'-'))
print(miao.send('鱼肉'))
结果:
Traceback (most recent call last):
--------------------------------------------------
File "C:/Users//Desktop/Python/cnblogs/subModule.py", line 67, in <module>
print(miao.send('鱼肉'))
TypeError: can't send non-None value to a just-started generator
看到了两个信息:
a)miao = cat() ,只是用于返回一个生成器对象,cat函数不会执行
b)can’t send non-None value to a just-started generator;不能给一个刚创建的生成器对象直接send值
改一下,情况2)
miao = cat()
miao.__next__()
print(miao.send('鱼肉'))
那么到底send()做了什么呢?send()的帮助文档写的很清楚,’’‘Resumes the generator and “sends” a value that becomes the result of the current yield-expression.’’’;可以看到send依次做了两件事:
a)回到生成器挂起的位置,继续执行b)并将send(arg)中的参数赋值给对应的变量,如果没有变量接收值,那么就只是回到生成器挂起的位置
但是,我认为send还做了第三件事:
c)兼顾__next__()作用,挂起程序并返回值,所以我们在print(miao.send(‘鱼肉’))时,才会看到’好开心’;其实__next__()等价于send(None)
所以当我们尝试这样做的时候:
def cat():
print('我是一只hello kitty')
while True:
food = yield
if food == '鱼肉':
yield '好开心'
else:
yield '不开心,人家要吃鱼肉啦'
miao = cat()
print(miao.__next__())
print(miao.send('鱼肉'))
print(miao.send('骨头'))
print(miao.send('鸡肉'))
就会得到这个结果:
我是一只hello kitty
None
好开心
None
不开心,人家要吃鱼肉啦
我们按步骤分析一下:
a)执行到print(miao.next()),执行cat()函数,print了”我是一只hello kitty”,然后在food = yield挂起,并返回了None,打印None
b)接着执行print(miao.send(‘鱼肉’)),回到food = yield,并将’鱼肉’赋值给food,生成器函数恢复执行;直到运行到yield ‘好开心’,程序挂起,返回’好开心’,并print’好开心’
c)接着执行print(miao.send(‘骨头’)),回到yield ‘好开心’,这时没有变量接收参数’骨头’,生成器函数恢复执行;直到food = yield,程序挂起,返回None,并print None
d)接着执行print(miao.send(‘鸡肉’)),回到food = yield,并将’鸡肉’赋值给food,生成器函数恢复执行;直到运行到yield’不开心,人家要吃鱼肉啦’,程序挂起,返回’不开心,人家要吃鱼肉啦’,并print ‘不开心,人家要吃鱼肉啦’
大功告成,那我们优化一下代码:
def cat():
msg = '我是一只hello kitty'
while True:
food = yield msg
if food == '鱼肉':
msg = '好开心'
else:
msg = '不开心,人家要吃鱼啦'
miao = cat()
print(miao.__next__())
print(miao.send('鱼肉'))
print(miao.send('鸡肉'))
我们再看一个更实用的例子,一个计数器。
def counter(start_at = 0):
count = start_at
while True:
val = yield count
if val is not None:
count = val
else:
count += 1
count = counter(5)
print(count.__next__())
print(count.send(0))
结果为5,0而不是5,6的原因:
①执行print(count.next()),程序运行到val = yield count(第一个yield语句)后挂起,然后返回yield后面的值,所以结果为5。
②执行print(count.send(0)),程序恢复到挂起点val = yield count,将send的参数0赋值给接受变量val,然后继续执行下面的语句,由于val=0,所以if val is not None条件为真,count = val,接着又来到yield语句(val = yield count),此时程序挂起,返回yield后面的值count=0。
综上:当执行next()方法时,程序会恢复到挂起点,依次执行yield语句下面的语句,最后再返回yield后面的值;而不是先返回yield后面的值,再执行yield后面的语句。
最后给出一张图说明Iterable,Iterator,Generator的关系:
补充几个小例子:
a)使用生成器创建一个range
def range(n):
count = 0
while count < n:
yield count
count += 1