约定:
「yield值」:指「生成器函数」返回的值,即,yield关键字后面的表达式的值
可迭代对象(iterable)
「可迭代对象」:简单的概括就是,能从其获取一个「迭代器对象」的对象。
所有的序列类型(例如:list,str和tuple)和一些非序列类型(例如:dict,file)对象,以及任何用户定义的包含iter()函数或者getitem()函数的类的对象(一般为用作容器的对象)都是「可迭代对象」。
iter()函数:返回一个「迭代器对象」。
Python学习交流群:1004391443,这里是python学习者聚集地,有大牛答疑,有资源共享!小编也准备了一份python学习资料,有想学习python编程的,或是转行,或是大学生,还有工作中想提升自己能力的,正在学习的小伙伴欢迎加入学习。
如果没有定义iter()函数,则看有没有定义getitem()函数,如果有定义,则Python会创建(一般就是iter()函数创建)一个内置的默认「迭代器对象」,该「迭代器对象」会调用getitem()函数。
当一个「可迭代对象」作为参数传给内置的iter()函数时,iter()函数将返回一个「迭代器对象」(它会调用刚刚谈到的iter()以获得一个「迭代器对象」,或是自己创建一个「迭代器对象」调用getitem())。通常我们没有必要主动调用iter()函数,for循环语句自动调用iter()函数,在循环执行期间会暂存这个返回的「迭代器对象」。
迭代器对象(iterator)
首先了解一个协议,「迭代器协议」:
「迭代器协议」定义了两个函数:
iter():返回「迭代器对象」自己。(实现时,如果你很确定该对象肯定不会作为「可迭代对象」使用,也可以不实现这个函数,但是很显然,你不确定,所以还是实现这个函数吧。)
next():从容器中返回下一项,如果没有下一项了,则抛出StopIteration异常。
「迭代器类型」:遵循「迭代器协议」的类型
「迭代器对象」:「迭代器类型」的实例对象。
反复的调用「迭代器对象」的next()函数(将「迭代器对象」作为参数传递给内置的next()函数会调用「迭代器对象」的next()函数)将连续返回容器中的数据。当没有更多的数据可用时,将会抛出StopIteration异常,此时「迭代器对象」已经耗尽了它的数据,以后再次调用它的next()都只会抛出StopIteration异常(可以概括为:对于这个「迭代器对象」,一次抛出StopIteration,终身抛出StopIteration)。
「迭代器对象」的iter()函数返回「迭代器对象」本身,这样每个「迭代器对象」也是一个「可迭代对象」,可使用「可迭代对象」的任何地方都可以使用「迭代器对象」。
有一点需要注意:一个「可迭代对象」每次作为参数调用iter()函数或者用于for循环时,都将生成一个新的迭代器对象。
下面我们来实现一个自己的「可迭代对象」和「迭代器对象」:
这是一个迭代器类型
class iterObj:
def iter(self):#实现这个函数,便可执行iter(iterObj对象)
return self
def next(self):#实现这个函数,便是迭代器类型
if self.index == 0:
raise StopIteration
self.index = self.index – 1
return self.data[self.index]
这是一个可迭代类型
class Reverse:
def init(self, data):
self.data = data
self.index = len(data)
def iter(self):#实现了这个函数,便是可迭代的
iter = iterObj()
iter.data = self.data
iter.index = len(self.data)
return iter
# def getitem(self, index):#如果没有实现iter函数,但是实现了这个函数,则python创建的迭代器对象会调用该函数。
#如果想提前结束,可以抛出异常
# if index == 1:
# raise StopIteration
# return self.data[index]
rev = Reverse(‘spam’)
for char in rev:
print(char)
关于iter()函数
通常使用iter()函数都只是传递一个参数,但其实它接受两个参数:
iter(object[,sentinel]):如果只传了第一个参数,那么这个对象必须实现了iter()函数或者实现了getitem()函数,否则会抛出TypeError的异常。在我们刚刚编写的例子就符合要求。
如果传递了第二个参数,那么第一个参数必须是「可调用的对象」(例如函数),iter()函数会创建一个「迭代器对象」,这个「迭代器对象」的next()函数会调用第一个参数,当第一个参数返回的值等于第二个参数时,抛出StopIteration异常,否则返回该值。来看一个例子:
class Reverse:
def init(self, data):
self.data = data
self.index = len(data)
def callable(self):
if self.index == 0:
raise StopIteration
self.index = self.index – 1
return self.data[self.index]
rev = Reverse(‘spam’)
ite = iter(rev.callable, ‘a’)
print(ite)#<callable_iterator object at 0x1101fe5c0>
for char in ite:#打印m
print(char)
生成器对象(Generators)
「生成器对象」其实也是「迭代器对象」,因为从「生成器对象」中获取「迭代器对象」时只是简单的把自己返回了(类似我们前面自己编写的例子),但是它特殊就特殊在yield语句上。
当一个函数中含有yield语句时,那么称这个函数为「生成器函数」。
当我们定义了一个「生成器函数」时,iter()和next()会被自动定义(不论是通过一个内置的「生成器类」还是其他办法,总之,这两个函数被自动定义了),当调用这个「生成器函数」时,会返回一个「生成器对象」。
上一节我们自己编写了一个「迭代器」例子,相比较后我们可以猜到,其实Python为「生成器」自动创建的那两个函数与我们自己写的在目的上没什么区别,无非都是为了实现「迭代器协议」,从这个角度来说,前面我们自己写的Reverse类的实例,也可以称为「生成器对象」,只是在具体实现上有区别,特别是next()函数,至于「生成器对象」的其他特性,我们照样可以自己加上。
「生成器对象」的next()函数会启动「生成器函数」的执行或者从上一次执行yield语句的地方恢复执行,直到遇到下一个yield语句后,挂起(或者称为暂停),同时将yield关键字后面的表达式的值返回给调用者(对于挂起,我们只需要知道Python会把当时的执行状态保存下来以便下次恢复)。来看一个例子:
def reverse(data):
print(‘reverse is called’)#3
for index in range(len(data)-1, -1, -1):#4 #10
yield data[index]#5 #8 #11
print(‘执行yield表达式以后的语句’)#9
这里并没有输出’reverse is called’,仅仅是创建了一个生成器对象,并没有执行reverse函数。
re = reverse(‘golf’)#1
for语句会调用iter(re),接着会调用返回对象的next()函数
for char in re:#2 #7
print(char)#6
输出:f l o g
执行过程是这样的:
1 创建一个「生成器对象」。
2 for语句会调用iter(re),返回对象其实就是re本身(其实这里也相当于创建了一个迭代器),接着会调用返回对象的next()函数,next()函数启动执行「生成器函数」。
3 打印
4 执行for循环的第一次循环
5 遇到「yield表达式」挂起,返回「yield值」(这里就是:data[index]),执行流回到调用者
6 此时char已经被赋值,就是#5中「yield表达式」的值,打印
7 继续执行for循环,再次调用next()
8 从步骤5挂起的地方恢复执行
9 执行打印:执行「yield表达式」以后的语句
10 继续执行for循环
11 再次遇到「yield表达式」回到步骤#5
直到这个由「生成器对象」创建的「迭代器对象」耗尽,执行结束。
yield表达式
到这里是不是觉得对yield有点感觉了?请先在新里面默念三遍:「yield 100」是一个表达式。
我们接着来看,下面例子中m的值分别为多少?
def yield_func():
print(‘— first next() is called—‘)
m = yield 100 #1
print(‘— second next() is called—’)
print(m)
m = yield 100 #2
print(‘— send(value) is called—’)
print(m)
yld = yield_func()
next(yld)#输出— first next() is called—
next(yld)#输出—-second next() is called—- 和 None
yld.send(1)#输出—-send(value) is called—-和 1 并且抛出异常:StopIteration
上面m的值依次打印为None和1:
当第一个next()执行的时候,仅仅打印一行文字;
然后执行第二个next()函数,此时从#1处恢复,恢复时yield 100表达式的返回值赋给了m,那这个表达式的值是多少?从后面的打印可以看到是None;
然后是执行yld.send(1),此时从#2处恢复,恢复时仍然是把yield 100表达式的值赋给了m,这次打印出来m的值为:1,恰好是我们传进去的值。
结论:
「生成器对象」的send(value)函数会恢复执行「生成器函数」,同时把参数作为当前恢复的「yield表达式」的返回值,而next()(其内部调用了「生成器对象」的next()函数)相等于send(None),即,当通过next()恢复时,当前恢复的「yield表达式」的值为None。
至于最后的那个异常,这是常规操作,当一个迭代器迭代完成后,就会抛出这个异常,上一节我们自己实现的那个迭代器,也是抛出这个异常,之前这个异常为什么没有中断程序是因为内置的Python实现帮我们捕获了这个异常。
需要注意一点:由于最开始时并没有「yield表达式」接收这个传进来的值,所以当启动「生成器函数」时,需要传空值,即send(None)或者使用next()
「生成器对象」(除了next()和send(value))还有其他两个函数:
generator.throw(type[,value[,traceback]]):从「生成器函数」挂起的地方抛出一个type类型的异常,并返回下一个「yield值」,如果「生成器函数」没有返回「yield值」就退出了,那么会抛出StopIteration异常。
generator.close():从「生成器函数」挂起的地方抛出一个GeneratorExit异常。如果此 后「生成器函数」正常执行结束则关闭(此时抛出的GeneratorExit异常被捕获了),如果继续返回「yield值」则会抛出RuntimeError。
最后来看一个综合的例子:
def echo(value=None):
print(“Execution starts when ‘next()’ is called for the first time.”)
try:
while True:
try:
value = (yield value)
except Exception as e:
value = e
#break #这里如果直接beak退出循环,则「生成器函数」执行结束退出,调用generator.throw(TypeError, “spam”)时,会抛出StopIteration异常
#except: #这里如果捕获所有的异常,则while循环会继续执行,此时会close()抛出RuntimeError
# pass
finally:
print(“Don’t forget to clean up when ‘close()’ is called.”)
generator = echo(1)
print(next(generator))#输出:Execution starts when ‘next()’ is called for the first time.和 1
print(next(generator))#输出:None
print(generator.send(2))#输出:2
generator.throw(TypeError, “spam”)#输出:TypeError(‘spam’,)
generator.close()#输出:Don’t forget to clean up when ‘close()’ is called.
这里提醒一下:例子中如果抛出的异常被捕获,仅仅执行赋值,然后while循环会继续执行。