可迭代对象(Iterable)
Python中内置的序列,如list、tuple、str、bytes、dict、set、collections.deque等都是可迭代对象,但它们不是迭代器。
可迭代对象指使用iter()内置函数可以获取迭代器(Iterator)的对象
Python解释器需要迭代对象x时,会自动调用iter(x) — — 我们看不到
可迭代对象:
(1)如果对象实现了能返回迭代器的__iter__()方法。
(2)如果对象实现了__getitem__(index)方法,而且index参数是从0开始的整数(索引)。
(3)Python内置的序列都是可迭代,因为他们都实现了__getitem__()方法,而且还都实现了__iter__()方法。
iter()的作用:
(1)检查对象x是否都实现了__iter__()方法,如果都实现了该方法就调用它,并尝试获取一个迭代器。
(2)如果没有实现__iter__()方法,但是实现了__getitem__(index)方法,尝试按顺序(从索引0开始)获取元素,即参数index时从0开始的整数(int)。之所以会检查是否实现__getitem__(index)方法,是为了向后兼容
(3)如果前面的都尝试失败,Python会抛出TypeError异常,通常会提示'X' object is not iterable(X类型的对象不可迭代),其中X时目标对象所属的类。
- 判断对象是否为可迭代
详细说明如下:
从Python 3.4开始,检查对象x能否迭代,
最准确的方法是:调用iter(x)函数,如果不可迭代,会抛出TypeError异常。
这比使用isinstance(x, abc.Iterable)更准确,
因为iter(x)函数会考虑到遗留的__getitem__(index)方法,而abc.Iterable类则不会考虑
- getitem()
# 【例】构造一个类,实现__getitem__()方法。
# 可以给累的构造方法传入包含一些文本的字符串,然后可以逐个单词进行迭代
# 创建一个test.py模块
from test import Sentence # test.py模块导入该模块中的Sentence类
s = Sentence('I love yashu') # 传入字符串,创建一个Sentence实例
print(s)
# —— Sentence('I love yashu')
print(s[0])
print(s.__getitem__(0))
# —— I
# —— I
# Sentence实例可迭代
for word in s:
print(word)
# I
# love
# yashu
# 因为可迭代,所以Sentence对象可以用于构建列表和其他可迭代的类型
print(list(s))
print(tuple(s))
print(set(s))
# ['I', 'love', 'yashu']
# ('I', 'love', 'yashu')
# {'yashu', 'I', 'love'}
from collections import abc
print(isinstance(s, abc.Iterable)) # 不能正确判断Sentence类的对象s时可迭代对象
# —— False
print(iter(s)) # 没有跑出异常,返回迭代器,说明Sentence类的对象s是可迭代的
# —— <iterator object at 0x0376CFF0>
# 结论:isinstance(o, t)会有误判,最好的判断方式是用iter(iterable)
- iter()
# iter()函数的补充
# 【例1-1】如果实现了__iter__()方法,但该方法没有返回迭代器时
class Foo:
def __iter__(self):
pass
from collections import abc
f = Foo()
print(isinstance(f, abc.Iterable)) # 错误地判断Foo类的对象f是可迭代对象
# —— True
# print(iter(f)) # 使用iter()方法会抛出异常,即对象f不可迭代,不能用for循环迭代它
# —— TypeError: iter() returned non-iterator of type 'NoneType'
# 注:Python迭代协议要求iter()必须返回特殊的迭代器对象。
# 【例1-2】如果实现了__iter__()方法,该方法有返回迭代器时
class Foo:
def __iter__(self): # 将迭代请求委托给列表
return iter([1, 2, 3]) # iter()函数从列表创建迭代器,等价于[1, 2, 3].__iter__()
from collections import abc
f = Foo()
print(isinstance(f, abc.Iterable))
# —— True
print(iter(f))
# —— <list_iterator object at 0x02BF74F0>
for i in f:
print(i)
# 1
# 2
# 3
- iter()函数的补充
# iter()函数有两种用法:
# (1)iter(iterable) --> iterator:传入可迭代对象,返回迭代器
# (2)iter(callable, sentinel) --> iterator:传入两个参数,第一个参数必须是可调用的对象,用于不断调用(没有参数),产出各个值;
# 第二个值是哨符,是一个标记值,当可调用的对象返回这个值时,出发迭代器跑出Stoplteration异常,而不产出哨符。
# 【例1】使用iter()函数的第二种用法来掷骰子,直到掷出1点为止
from random import randint
def d6():
return randint(1, 6)
d6_iter = iter(d6, 1) # 用了iter(callable, sentinel)格式,第一个参数为d6函数,第二个参数为哨符
print(d6_iter) # 这里的iter函数返回一个callable_iterator对象
# —— <callable_iterator object at 0x033A85D0>
# for 循环可能运行特别长的时间,不过肯定不会打印 1,因为 1 是哨符
for roll in d6_iter:
print(roll)
# 5
# 【例2】逐行读取文件,直到遇到空行或者到达文件末尾为止
# 创建mydata.txt文件
with open('mydata.txt', encoding='utf-8') as f:
# f.readline每次返回一行
for line in iter(f.readline, '\\t\n'): # = 哨符
print(line)
# 结论:iter(callable,sentinel)两个参数(可调用对象,哨符) , 重复调用callable,直到返回的值为哨符就会停止迭代,抛出异常StopIteration
迭代器
Python从可迭代的对象中获取迭代器。
迭代器可以被 next() 函数调用,并不断返回下一个值。
迭代器用于从集合中取出元素
迭代器为了惰性求值(lazy evaluation),避免浪费内存空间,实现高效处理大量数据。
迭代器对象必须实现无参数的next()方法,返回序列中的下一个元素,并使用StopIteration异常来通知迭代结束(即没有元素时抛出StopIteration异常或迭代器可以被next()函数调用,并不断返回下一个值。)
迭代器模式(Iterator pattern):当扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。
在 Python 语言内部,迭代器用于支持:
①for 循环
②构建和扩展集合类型
③逐行遍历文本文件
④列表推导、字典推导和集合推导
⑤元组拆包
⑥调用函数时,使用 * 拆包实参
- 判断对象是否为迭代器
# 检查对象x是否为迭代器最好的方式是调用 isinstance(x, abc.Iterator)
# 【例】
from collections import abc
print(isinstance([1, 3, 5], abc.Iterator))
# —— False
print(isinstance((2, 4, 6), abc.Iterator))
# —— False
print(isinstance({'name': 'wangy', 'age': 18}, abc.Iterator))
# —— False
print(isinstance({1, 2, 3}, abc.Iterator))
# —— False
print(isinstance('abc', abc.Iterator))
# —— False
print(isinstance(100, abc.Iterator))
# —— False
print(isinstance((x*2 for x in range(5)), abc.Iterator)) # 生成器表达式(生成器推导式)
# —— True
# 注:1. Python中内置的序列类型,如list、tuple、str、bytes、dict、set、collections.deque等都是可迭代的对象,但不是迭代器;
# 2. 生成器一定是迭代器
- next()和iter()
# 标准的迭代器接口:
# (1)__next__():返回下一个可用的元素,如果没有元素了,抛出StopIteration异常。调用next()相当于调用x.__next__()
# (2)__iter__():返回迭代器本身(self),以便在应该使用可迭代的对象的地方能够使用迭代器,
# 比如在for循环、list(iterable)函数、sum(iterable, start=0, /)函数等应该使用可迭代的对象地方可以使用迭代器。
# 说明只要实现了能返回迭代器的__iter__()方法的对象就是可迭代的对象,所有迭代器都是可迭代的对象。
# 【例】Sentence类的对象是可迭代的对象,而SentenceIterator类实现了典型的迭代器设计模式
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
return SentenceIterator(self.words) # 迭代协议要求__iter__返回一个迭代器
class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index] # 获取 self.index 索引位(从0开始)上的单词。
except IndexError:
raise StopIteration() # 如果 self.index 索引位上没有单词,那么抛出 StopIteration 异常
self.index += 1
return word
def __iter__(self):
return self # 返回迭代器本身
- next()函数获取迭代器中下一个元素
# 除了可以使用for循环处理迭代器中的元素以外,还可以使用next()函数,实际上是调用iterator.__next__(),每调回一次该函数,就返回迭代器的下一个元素。
# 如果已经是最后一个元素了,再继续调用next()就会抛出StopIteration异常。
# 一般来说,StopIteration异常是用来通知我们迭代结束的
with open('mydata.txt', encoding='utf-8') as fd:
try:
while True:
line = next(fd)
print(line, end='')
except StopIteration:
pass
# 类似的还有:
# fullmatch(pattern, string, flags=0)
# compile(pattern, flags=0)
# purge()
# template(pattern, flags=0)
- 可迭代的对象与迭代器的对比
# 可迭代对象和迭代器之间的关系:Python从可迭代对象中获取迭代器
# 【例1-1】用for循环迭代一个字符串'ABC',字符串是可迭代对象。for循环的背后会先调用iter(s)将字符串转换成迭代器,只不过我们看不到
s = 'ABC'
for char in s:
print(char)
# A
# B
# C
# 【例1-2】如果没有for循环,就不得不使用while循环来模拟
s = 'ABC'
it = iter(s) # 使用可迭代的对象s构建迭代器it
while True:
try:
print(next(it))
except StopIteration:
del it
break
# A
# B
# C
# 结论:Stoplteration异常表明迭代器到头了。
# 而Python语言内部会处理for循环和其他迭代上下文(如列表推导、元组拆包等)中的Stoplteration异常
# 【例】使用iter()函数来构建迭代器,并使用next()函数依次获取迭代器中的元素
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
return SentenceIterator(self.words) # 迭代协议要求__iter__返回一个迭代器
class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index] # 获取 self.index 索引位(从0开始)上的单词。
except IndexError:
raise StopIteration() # 如果 self.index 索引位上没有单词,那么抛出 StopIteration 异常
self.index += 1
return word
def __iter__(self):
return self # 返回迭代器本身
from test import Sentence
s = Sentence('Hiro and Sylar')
it = iter(s) # 获取迭代器
print(it)
# —— <iterator object at 0x015EC0D0>
print(next(it)) # 使用next()方法获取下一个单词
print(it.__next__()) # it.__next__() = next(it),但我们应该避免直接调用特殊方法
print(next(it))
# Hiro
# and
# Sylar
# print(next(it)) # 没有单词了,因此迭代器抛出StopIteration异常
# —— StopIteration
# 如果想再次迭代,就要重新构建迭代器
print(list(iter(s)))
print(next(it))
print(next(it))
print(next(it))
# Hiro
# and
# Sylar
# 如此类推
# 结论:(1)迭代器要实现__next__()方法,返回迭代器中的下一个元素
# (2)迭代器还要实现__iter__()方法,返回迭代器本身,因此,迭代器可以迭代。迭代器都是可迭代的对象
# (3)可迭代的对象一定不能是自身的迭代器。也就是说,可迭代的对象必须实现__iter__()方法,但不能实现__next__()方法
生成器
所有生成器都是迭代器,生成器完全实现了迭代器接口。
生成器用于"凭空"生成元素
生成器为了惰性求值(lazy evaluation),避免浪费内存空间,实现高效处理大量数据。
只要 Python 函数的定义体中有 yield 关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器(generator)对象。
注:普通的函数与生成器函数在语法上唯一的区别是,在后者的定义体中有 yield 关键字
- 生成器函数
# 【例1】
def gen_AB():
print('start')
yield 'X'
print('continue')
yield 'Y'
print('end')
print(gen_AB) # 生成器函数
# —— <function gen_AB at 0x02DDA540>
g = gen_AB() # 调用生成器函数,返回一个生成器对象
print(g)
# 生成器都是迭代器,执行next(g)时生成器函数会向前,前进到函数定义体中的下一个 yield 语句,生成 yield 关键字后面的表达式的值,在函数定义体的当前位置暂停,并返回生成的值
print(next(g)) # 激活生成器,打印start,生成 yield 关键字后面的表达式的值X,在函数定义体的当前位置暂停,并返回生成的值X
print(next(g)) # 打印continue,生成 yield 关键字后面的表达式的值Y,在函数定义体的当前位置暂停,并返回生成的值Y
# start
# X
# continue
# Y
# print(next(g)) # 打印end,到达生成器函数定义体的末尾时,生成器对象抛出StopIteration异常
# StopIteration
# 注:普通函数返回值,调用生成器函数返回生成器,生成器产出或生成值
# 调用生成器函数后,会构建一个实现了迭代器接口的生成器对象,即,生成器一定是迭代器!
for c in gen_AB():
print('-->', c)
# start
# --> X
# continue
# --> Y
# end
# 【例2-1】使用iter()函数来构建迭代器,并使用next()函数依次获取迭代器中的元素
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
return SentenceIterator(self.words) # 迭代协议要求__iter__返回一个迭代器
class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index] # 获取 self.index 索引位(从0开始)上的单词。
except IndexError:
raise StopIteration() # 如果 self.index 索引位上没有单词,那么抛出 StopIteration 异常
self.index += 1
return word
def __iter__(self):
return self # 返回迭代器本身
# 【例2-2】可以使用生成器函数改写上面Sentence类,此时不再需要SentenceIterator类
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
# 最简单是委托迭代给列表,这里仅演示生成器函数的用法
# return iter(self.words) # 等价于self.words.__iter__()
for word in self.words:
yield word # 产出当前的word
# 迭代器和生成器都是为了惰性求值(lazy evaluation),避免浪费内存空间。
# 而上面的Sentence类却不具备惰性,因为RE_WORD.findall(text)会创建所有匹配项的列表,然后将其绑定到 self.words 属性上。
# 如果我们传入一个非常大的文本,那么该列表使用的内存量可能与文本本身一样多,而假设我们只需要迭代前几个单词,那么将浪费大量的内存。
#
# re.finditer 函数是 re.findall 函数的惰性版本,返回的不是列表,而是一个迭代器,按需生成 re.MatchObject实例。
# 如果有很多匹配, re.finditer 函数能节省大量内存。我们要使用这个函数让 Sentence 类变得懒惰,即只在需要时才生成下一个单词
# 【例2-3】再改进。
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
# finditer()函数构建一个迭代器,包含 self.text 中匹配 RE_WORD 的单词,产出 MatchObject 实例
for match in RE_WORD.finditer(self.text):
yield match.group() # match.group() 方法从 MatchObject 实例中提取匹配正则表达式的具体文本
- 生成器表达式
# 简单的生成器函数(有yield关键字),可以替换成生成器表达式(没有yield关键字,将列表推导中的[]替换为()即可),让代码变得更简短
# 生成器表达式可以理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。
# 【例1】生成器函数和生成器表达式
def gen_AB(): # 生成器函数
print('start')
yield 'X'
print('continue')
yield 'Y'
print('end')
res1 = [x*3 for x in gen_AB()] # 列表推导迫切地迭代 gen_AB() 函数生成的生成器对象产出的元素: 'A' 和 'B'。注意,下面的输出是 start、 continue 和 end
print(res1) # 生成器
# start
# continue
# end
# ['XXX', 'YYY']
# 这个 for 循环迭代列表推导生成的 res1 列表
for i in res1:
print('-->', i)
# --> XXX
# --> YYY
res2 = (x*3 for x in gen_AB()) # 把生成器表达式返回的值赋值给 res2。只需调用 gen_AB() 函数,虽然调用时会返回一个生成器,但是这里并不使用
print(res2) # res2 是一个生成器对象
# —— <generator object <genexpr> at 0x01369B30>
# 只有 for 循环迭代 res2 时, gen_AB 函数的定义体才会真正执行。 for 循环每次迭代时会隐式调用 next(res2),前进到 gen_AB 函数中的下一个 yield 语句。
# 注意, gen_AB 函数的输出与 for 循环中 print 函数的输出夹杂在一起
for i in res2:
print(i)
# start
# XXX
# continue
# YYY
# end
# 结论:生成器表达式会产出生成器
# 【例2】使用生成器表达式进一步减少Sentence类的代码
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self): # 不再是生成器函数了(没有 yield),而是使用生成器表达式构建生成器
# finditer()函数构建一个迭代器,包含 self.text 中匹配 RE_WORD 的单词,产出 MatchObject 实例
# for match in RE_WORD.finditer(self.text):
# yield match.group() # match.group() 方法从 MatchObject 实例中提取匹配正则表达式的具体文本
return (match.group() for match in RE_WORD.finditer(self.text))
# 生成器表达式是创建生成器的简洁语法,这样无需先定义函数再调用。
# 生成器函数灵活得多,可以使用多个语句实现复杂的逻辑,也可以作为协程使用
# 遇到简单的情况时,可以使用生成器表达式,因为这样扫一眼就知道代码的作用。
# 如果生成器表达式要分成多行写,我倾向于定义生成器函数,以便提高可读性。此外,生成器函数有名称,因此可以重用
# 【例】如果将生成器表达式传入只有一个参数的函数时,可以省略生成器表达式外面的():
# 下面两行代码作用一样
print(list((x*2 for x in range(5))))
print(list(x*2 for x in range(5)))
# —— [0, 2, 4, 6, 8]
# —— [0, 2, 4, 6, 8]
- 嵌套的生成器
# 【例1-1】可以将多个生成器像管道(pipeline)一样链接起来使用,更高效的处理数据
# 第一个生成器:产出整数的生成器
def integers():
for i in range(1, 9):
yield i
chain = integers()
print(list(chain))
# —— [1, 2, 3, 4, 5, 6, 7, 8]
# 第二个生成器:基于整数的生成器,产出平方数的生成器
def squared(seq):
for i in seq:
yield i * i
chain = squared(integers())
print(list(chain))
# —— [1, 4, 9, 16, 25, 36, 49, 64]
# 第三个生成器:基于平方数的生成器,产出负的平方数的生成器
def negated(seq):
for i in seq:
yield -i
chain = negated(squared(integers()))
print(list(chain))
# —— [-1, -4, -9, -16, -25, -36, -49, -64]
# 【例1-2】使用生成器表达式进一步优化链式生成器
integers = range(1, 9)
squared = (i * i for i in integers)
negated = (-i for i in squared)
print(list(negated))
# —— [-1, -4, -9, -16, -25, -36, -49, -64]
# 结论:更简洁
- 增强生成器
# Python 2.5 通过了 PEP 342 -- Coroutines via Enhanced Generators ,这个提案为生成器对象添加了额外的方法和功能,其中最值得关注的是.send()方法
# 与.__next__()方法一样,.send()方法使生成器前进到下一个yield语句。
# 不过,.send()方法还允许调用方把数据发送给生成器,即不管传给.send()方法什么参数,那个参数都会成为生成器函数定义体中对应的yield表达式的值。
# 也就是说,.send()方法允许在调用方和生成器之间双向交换数据,而.__next__()方法只允许调用方从生成器中获取数据
# 查看生成器对象的状态:
# 可以使用 inspect.getgeneratorstate(...) 函数查看生成器对象的当前状态:
# print(inspect.getgeneratorstate(x))
# (1)'GEN_CREATED': 等待开始执行
# (2)'GEN_RUNNING': 正在被解释器执行。只有在多线程应用中才能看到这个状态
# (3)'GEN_SUSPENDED': 在yield表达式处暂停
# (4)'GEN_CLOSED': 执行结束
# 【例】增强器的使用
def echo(value=None):
print("Execution starts when 'next()' is called for the first time.")
try:
while True:
try:
value = (yield value) # 调用send(x)方法后,等号左边的value将被赋值为x
value += 1
print(f'--str({value})--')
except Exception as e:
value = e
finally:
print("Don't forget to clean up when 'close()' is called.")
g = echo(1) # 返回生成器对象,此时value=1
import inspect
print(inspect.getgeneratorstate(g))
# —— GEN_CREATED
print(next(g)) # 第一次要调用next()方法,让生成器前进到第一个yield处,后续才能在调用send()方法时,在该yield表达式位置接收客户发送的数据
# —— Execution starts when 'next()' is called for the first time.
# —— 1 # (yield value),产出value的值,因为此时value=1,所以打印1
print(inspect.getgeneratorstate(g))
# —— GEN_SUSPENDED
print(next(g)) # 第二次调用next()方法,相当于调用send(None),所以value = (yield value)中等号左边的value将被赋值为None。下一次While循环,又前进到(yield value)处,产出value的值,因为此时value=None,所以打印None
# —— None
print(g.send(99)) # 直接调用send(2)方法,所以value = (yield value)中等号左边的value将被赋值为2。下一次While循环,又前进到(yield value)处,产出value的值,因为此时value=2,所以打印2
# —— 100
print(g.throw(TypeError, "spam")) # 调用throw()方法,将异常对象发送给生成器,所以except语句会捕获异常,即value=TypeError('spam')。下一次While循环,又前进到(yield value)处,产出value的值,因为此时value=TypeError('spam'),所以打印TypeError('spam')
# —— spam
g.close() # 调用close()方法,关闭生成器
# —— Don't forget to clean up when 'close()' is called.
print(inspect.getgeneratorstate(g))
# —— GEN_CLOSED
# 注: 给已结束的生成器发送任何值,都将抛出StopIteration异常,且返回值(保存在异常对象的value属性上)是None
# print(g.send(3))
# —— StopIteration
# 结论:增强生成器是一项重要的 "改进",甚至改变了生成器的本性:像这样使用的话,生成器就变身为基于生成器的协程。
- yield from
# yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。
# 简单应用:拼接可迭代对象
# 【例1-1】拼接可迭代对象:使用yield
a_str = 'ABC'
a_list = [1, 2, 3]
a_dict = {'name': 'xuebi', 'age': 18}
a_gen = (i for i in range(4,8))
def gen(*args):
for item in args:
for i in item:
yield i
new_list=gen(a_str, a_list, a_dict,a_gen)
print(list(new_list))
# —— ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
# 【例1-2】拼接可迭代对象:使用yield from
a_str = 'ABC'
a_list = [1, 2, 3]
a_dict = {'name': 'xuebi', 'age': 18}
a_gen = (i for i in range(4, 8))
def gen(*args):
for item in args:
yield from item
new_list=gen(a_str, a_list, a_dict,a_gen)
print(list(new_list))
# —— ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
# yield from后面加上可迭代对象
# 它可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰。
# 复杂应用:生成器的嵌套
# 当 yield from 后面加上一个生成器后,就实现了生成的嵌套。
# 使用yield from可以让我们避免让我们自己处理各种料想不到的异常,而让我们专注于业务代码的实现。
# (1)调用方:调用委派生成器的客户端(调用方)代码
# (2)委托生成器:包含yield from表达式的生成器函数
# (3)子生成器:yield from后面加的生成器函数
# 【例2】生成器的嵌套,实现实时计算平均值
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
count += 1
total += new_num
average = total/count
# 委托生成器
def proxy_gen():
while True:
yield from average_gen()
# 调用方
def main():
calc_average = proxy_gen()
next(calc_average) # 预激下生成器
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0
if __name__ == '__main__':
main()
# 10.0
# 15.0
# 20.0
# calc_average = proxy_gen() --> next(calc_average)
# proxy_gen() --> yield from average_gen() --> average_gen()
# total = 0, count = 0, average = 0 --> new_num = yield average --> yield from average_gen() --> next(calc_average)
# print(calc_average.send(10)) -> count += 1 --> total += new_num --> average = total/count --> new_num = yield average --> yield from average_gen() --> print(calc_average.send(10))
# 下面两个同理
# 委托生成器的作用是:在调用方与子生成器之间建立一个双向通道(即调用方可以通过send()直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。)
# 注:子生成器yield回来的值,没有被委托生成器给拦截
# 【例3】测试子生成器yield回来的值,是否会被委托生成器给拦截
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
if new_num is None:
break
count += 1
total += new_num
average = total/count
# 每一次return,都意味着当前协程结束。
return total,count,average
# 委托生成器
def proxy_gen():
while True:
# 只有子生成器要结束(return)了,yield from左边的变量才会被赋值,后面的代码才会执行。
total, count, average = yield from average_gen()
print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))
# 调用方
def main():
calc_average = proxy_gen()
next(calc_average) # 预激协程
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0
calc_average.send(None) # 结束协程
# 如果此处再调用calc_average.send(10),由于上一协程已经结束,将重开一协程
if __name__ == '__main__':
main()
# 10.0
# 15.0
# 20.0
# 计算完毕!!
# 总共传入 3 个数值, 总和:60,平均数:20.0
# 如果我们去掉委托生成器,而直接调用子生成器。
# 那我们就需要把代码改成像下面这样,我们需要自己捕获异常并处理,而不像使yield from那样省心。
# 【例4】使用yield from处理异常
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
if new_num is None:
break
count += 1
total += new_num
average = total/count
return total,count,average
# 调用方
def main():
calc_average = average_gen()
next(calc_average) # 预激协程
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0
# ----------------注意-----------------
try:
calc_average.send(None)
except StopIteration as e:
total, count, average = e.value
print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))
# ----------------注意-----------------
if __name__ == '__main__':
main()
# 10.0
# 15.0
# 20.0
# 计算完毕!!
# 总共传入 3 个数值, 总和:60,平均数:20.0