This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.
Python-Generator-Iterator-Decorater
迭代器
迭代器指的是迭代取值的工具,迭代是一个重复的过程,并且每次重复都是基于上一次的结果而继续的,
单纯的重复并不是迭代!
可迭代对象
元组; 列表; 字典; 集合; 字符串等
生成器.
用 for 循环迭代
验证一个东西是否可迭代
可迭代不一定是迭代对象
(列表不是可迭代对象,但是可迭代.)
from collections import Iterable # 判断是否是可迭代对象(迭代器) isinstance("adb",Iterable) # instance:实例
生成器可迭代,但可以迭代的不一定是生成器
iter()
可以将列表变成可迭代对象
迭代器的作用
迭代器是用来迭代取值的工具,而涉及到把多个值循环取出来的类型
有:列表、字符串、元组、字典、集合、打开文件
l = ['egon', 'liu', 'alex']
i = 0
while i < len(l):
print(l[i])
i += 1
上述迭代取值的方式只适用于有索引的数据类型:列表、字符串、元组.
为了解决基于索引迭代器取值的局限性
python 必须提供一种能够不依赖于索引的取值方式,这就是迭代器.
迭代器详解
但凡内置有 __iter__()
方法的对象都称之为可迭代的对象:
s1=''
s1.__iter__()
l=[]
l.__iter__()
t=(1,)
t.__iter__()
d={'a':1}
d.__iter__()
set1={1,2,3}
set1.__iter__()
with open('a.txt',mode='w') as f:
f.__iter__()
pass
迭代器对象:
内置有 __next__
方法并且内置有 __iter__
方法的对象
迭代器对象 __next()__
:得到迭代器的下一个值.
迭代器对象 __iter__()
:得到迭代器的本身,调用和没调用相同.
d={'a':1,'b':2,'c':3}
d_iterator=d.__iter__()
print(d_iterator) # <dict_keyiterator object at 0x0000022FF0E13A90>
print(d_iterator.__next__()) # a
print(d_iterator.__next__()) # b
print(d_iterator.__next__()) # c
print(d_iterator.__next__()) # 抛出异常StopIteration,值取完了
d={'a':1,'b':2,'c':3}
d_iterator=d.__iter__()
while True:
try:
print(d_iterator.__next__())
except StopIteration:
break
# 得到"a","b","c"
print('====>>>>>>') # 在一个迭代器取值取完后,再对其取值就会报错
d_iterator=d.__iter__()
while True:
try:
print(d_iterator.__next__())
except StopIteration:
break
# 什么都得不到,因为迭代器值已取完
l=[1,2,3,4,5]
l_iterator=l.__iter__()
while True:
try:
print(l_iterator.__next__())
except StopIteration:
break
# 得到1,2,3,4,5
常见数据类型的分类:
可迭代对象:字符串、列表、元组、字典、集合、文件对象
迭代器对象:文件对象
s1=''
s1.__iter__()
l=[]
l.__iter__()
t=(1,)
t.__iter__()
d={'a':1}
d.__iter__()
set1={1,2,3}
set1.__iter__()
with open('a.txt',mode='w') as f:
f.__iter__()
f.__next__()
创建一个迭代器
把一个类作为一个迭代器使用需要在类中实现两个方法 iter()
与 next()
。
iter()
方法返回一个特殊的迭代器对象,这个迭代器对象实现了next()
方法并通过StopIteration
异常标识迭代的完成。next()
方法会返回下一个迭代器对象。StopIteration
StopIteration
异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发StopIteration
异常来结束迭代。
创建一个返回数字的迭代器,初始值为 1,逐步递增 1
class MyNumbers:
def __iter__(self):
# 初始化迭代器
self.a = 1
return self
def __next__(self):
# 迭代器返回值
if self.a <= 20:
x = self.a
self.a += 1
return x
else:
raise StopIteration
myclass = MyNumbers()
myiter = iter(myclass)
for x in myiter:
print(x)
# fun2:
myiter = iter(myclass)
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
For 循环的工作原理
for 循环可以称之为叫迭代器循环
d = {'a':1,'b':2,'c':3}
d.iter()
得到一个迭代器对象迭代器对象.next()
拿到一个返回值,然后将该返回值赋值给 k- 循环往复,直到抛出
StopIteration
异常for 循环会捕捉异常然后结束循环.
d = {'a':1,'b':2,'c':3}
for k in d: # d_it = d.__iter__()
print(k) # 循环k = d_it.__next__()直到抛出异常结束循环
with open('a.txt',mode='rt',encoding='utf-8') as f:
for line in f: # f.__iter__()
print(line)
list('hello') # 原理同for循环
生成器
在 Python 中,使用了 yield
的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
在调用生成器运行的过程中,每次遇到 yield
时函数会暂停并保存当前所有的运行信息,返回 **yield
的值**, 并在下一次执行 next()
方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象。
生成器的使用
def func():
print('第一次')
yield 1
print('第二次')
yield 2
print('第三次')
yield 3
print('第四次')
g = func()
print(g)
# 生成器就是迭代器
# 生成器函数返回的是可迭代对象.
g.__iter__()
# g.__next__()会触发函数体代码的运行,然后遇到yield停下来,将yield后的值
# 当做本次调用的结果返回
g = func()
# 可以用iter将可迭代对象转化为迭代器.用next()方法
print(g)
g.__iter__()
res1 = g.__next__()
# 第一次
print(res1)
# 1
res2 = g.__next__()
# 第二次
print(res2)
# 2
res3 = g.__next__()
# 第三次
print(res3)
# 3
res4 = g.__next__()
# 第四次
# StopIteration
实例: 使用 yield
实现斐波那契数列:
def fibonacci(n): # 生成器函数 - 斐波那契
a, b, counter = 0, 1, 0
while True:
if (counter > n):
return
yield a
a, b = b, a + b
counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
while True:
try:
print(next(f), end=" ")
except StopIteration:
break
yield
关键字
yield 关键字的作用是函数与调用者的通信,它不光能够将值返回给调用者,它还可以接收调用者传过来的值,那么怎么实现呢? 调用者通过 send()
函数将值传递给generator,generator通过 yield
前面的变量 (形如:n = yield)
来接收
>>> def test():
number = 0
while True:
n = yield number
print('我是yield关键字下面一行代码')
print('调用者传递过来的值为:', n)
>>> t = test()
>>> t.send(None)
# 当第一次执行生成器的时候,需要传入None
>>> t.send(1)
我是yield关键字下面一行代码
调用者传递过来的值为: 1
>>> t.send(2)
我是yield关键字下面一行代码
调用者传递过来的值为: 2
t.close() 关闭generator后再调用,会出现StopIteration异常
>>> next(t)
我是yield关键字下面一行代码
调用者传递过来的值为: None
可以发现,
send
函数和next
函数的区别就是send
函数可以在执行 generator 的过程中,给generator发送消息。而 next 仅仅是接收 yield 右边的变量值。
send()
方法用法
send 过来的数值 = yield next 出来的数值
它的主要作用是,当我需要手动更改生成器里面的某一个值并且使用它,则send 发送进去一个数据,然后保存到 yield 语句的返回值,以提供使用.
send()
的返回值就是那个本来应该被迭代出来的那个值。这样既可以保证我能够传入新的值,原来的值也不会弄丢.实例:
def my_generator(n): '''定义一个生成器''' for i in range(n): # send来的数值 = yield 数值 num = yield i print(f'传入的{num}') g = my_generator(5) print(next(g)) print(next(g)) a = g.send(100) print('我是{0}'.format(a)) print(next(g)) print(next(g)) ''' 结果 0 传入的None 1 传入的100 我是2 传入的None 3 传入的None 4 '''
throw()
方法用法
简单来说就是:向生成器扔去一个异常,使生成器停止.
raise exception in generator,return next yielded value or StopIteration
,即在生成器中抛出异常,并且这个 throw 函数会返回下一个要迭代的值或者是StopIteration。还是通过几个例子来看吧!
def my_generator():
yield 'a'
yield 'b'
yield 'c'
g = my_generator()
print(next(g))
print(next(g))
print('-------------------------')
print(g.throw(StopIteration))
print(next(g))
'''运行结果为:
a
b
-------------------------
StopIteration
'''
生成器的启动与关闭 close
生成器的启动
方法 1:**直接使用****
next(g)
****,这会**直接开始迭代第一个元素(推荐使用这个启动)方法 2:**使用****
g.send(None)
****进行启动,注意**第一次启动的时候只能传入**None
,如果传入其他具体的指则会报错**哦!def my_generator(): yield 1 yield 2 yield 3 yield 4 g = my_generator() g.send(None) #第一次启动,本来第一次应该迭代的1,这里被取代了,但是send(None)会返回1 print(next(g)) print(next(g)) print(next(g)) '''运行结果为: 2 3 4 '''
生成器的关闭
如果一个生成器被中途关闭之后,在此调用
next()
方法,则会显示错误,如下:def my_generator(): yield 1 yield 2 yield 3 yield 4 g = my_generator() print(next(g)) print(next(g)) g.close() print(next(g)) #在此处会显示错误 print(next(g)) '''运行结果为: 1 2 显示StopIteration '''
生成器的终止迭代——**StopIteration
**
前面讲的手动关闭生成器,使用
close()
方法,后面的迭代会抛出StopIteration
异常。
在一个生成器中,如果没有 return
,则默认执行到函数完毕时返回 StopIteration
:
def g1():
yield 1
g = g1()
next(g) #第一次调用next(g)时,会在执行完yield语句后挂起,所以此时程序并没有执行结束。
next(g) #程序试图从yield语句的下一条语句开始执行,发现已经到了结尾,所以抛出StopIteration异常。
'''运行结果为:
1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
'''
如果遇到 return
,如果在执行过程中 return,则直接抛出 StopIteration
终止迭代:
def g2():
yield 'a'
return
yield 'b'
g=g2()
next(g) #程序停留在执行完yield 'a'语句后的位置。
next(g) #程序发现下一条语句是return,所以抛出StopIteration异常,这样yield 'b'语句永远也不会执行。
'''运行结果为:
a
b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
'''
如果在 return
后返回一个值,那么这个值为 StopIteration
异常的说明,不是程序的返回值。
def g3():
yield 'a'
return '这是错误说明'
yield 'b' #有一些编辑器会提示错误,此处为unreachable code,即不可到达的代码
g=g3()
next(g)
next(g)
'''运行结果为:
a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: 这是错误说明
'''
注意:**生成器没有办法使用****
return
****来返回值。因为****return
****返回的那个值是通过**StopIteration 的异常信息返回的,所以没办法直接获取这个 return 返回的值。
当然上面所说的无法获取return 返回值,我们指的是没有办法通过 result=g3()
这种形式获取return 的返回值。实际上还是有手段获取这个return 的值的,有两种方法:
使用
yield from
语句因为
return
返回的值是作为StopIteration 的一个 value 属性存在的,StopIteration 本质上是一个类,所以可以通过访问它的value 属性获取这个 return 返回的值。使用下面的代码:def g3(): yield 'a' return '这是错误说明' yield 'b' g=g3() try: print(next(g)) #a print(next(g)) #触发异常 except StopIteration as exc: result=exc.value print(result) '''运行结果为: a 这是错误说明 '''
yield
实现协程
在 Python 中,协程通过 yield
实现。因为当一个函数中有 yield 存在的时候,这个函数是生成器,那么当你调用这个函数的时候,你在函数体中写的代码并没有被执行,而是只返回了一个生成器对象,这个需要特别注意。然后,你的代码将会在每次使用这个生成器的时候被执行。
前面讲过yield 表达式的两个关键作用:①返回一个值、②接收调用者的参数
“调用者”与“被调用者”之间的通信是通过 send()
进行联系的
正是因为 yield
实现的生成器具备“中断等待的功能”,才使得 yield 可以实现协程。
例子 1:生产者 - 消费者模型
def consumer(): r = '' while True: n = yield r #执行的中断点 if not n: return print('[消费者] 正在消费:{0}'.format(n)) r = '200 人民币' def produce(c): c.send(None) #启动消费者(生成器)——实际上是函数调用,只不过生成器不是直接象函数那般调用的 n = 0 while n < 5: n = n + 1 print('[生产者] 正在生产:{0}'.format(n)) r = c.send(n) #给消费者传入值——实际上也是函数调用 print('[生产者] 消费者返回:{0}'.format(r)) print('-------------------------------------------------') c.close() c = consumer()#构造一个生成器 produce(c) '''运行结果为: [生产者] 正在生产:1 [消费者] 正在消费:1 [生产者] 消费者返回:200 人民币 ------------------------------------------------- [生产者] 正在生产:2 [消费者] 正在消费:2 [生产者] 消费者返回:200 人民币 ------------------------------------------------- [生产者] 正在生产:3 [消费者] 正在消费:3 [生产者] 消费者返回:200 人民币 ------------------------------------------------- [生产者] 正在生产:4 [消费者] 正在消费:4 [生产者] 消费者返回:200 人民币 ------------------------------------------------- [生产者] 正在生产:5 [消费者] 正在消费:5 [生产者] 消费者返回:200 人民币 ------------------------------------------------- '''
协程其实就是一个可以暂停执行的函数,并且可以恢复继续执行。那么 yield
已经可以暂停执行了,如果在暂停后有办法把一些 value
发回到暂停执行的函数中,那么 Python 就有了『协程』。于是有了, “把东西发送到已经暂停的生成器中” 的方法,这个方法就是 send()
。
yield from
的简单实现
yield 是每次“惰性返回”一个值,其实从名字中就能看出,yield from
是 yield
的升级改进版本,如果将yield 理解成“返回”,那么 yield from 就是“从什么(生成器)里面返回”,这就构成了 yield from
的一般语法,即:
yield from 生成器()
其实她等同于:
for item in 生成器():
yield item
这样的形式。我们通过一个简单例子来看:
def generator2():
yield 'a'
yield 'b'
yield 'c'
yield from generator1() #yield from iterable本质上等于 for item in iterable: yield item的缩写版
yield from [11,22,33,44]
yield from (12,23,34)
yield from range(3)
for i in generator2():
print(i,end=' , ')
'''运行的结果为:
a , b , c , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 11 , 22 , 33 , 44 , 12 , 23 , 34 , 0 , 1 , 2 ,
'''
yield from
后面可以跟的可以是 “ 生成器 、元组、 列表、range() 函数产生的序列等可迭代对象”
简单地说,yield from generator
。实际上就是返回另外一个生成器。而 yield
只是返回一个元素。从这个层面来说,有下面的等价关系:yield from iterable 本质上等于 for item in iterable: yield item 。
yield from
的高级应用
1. 针对 Yield 无法获取生成器 return 的返回值
在使用yield 生成器的时候,如果使用for 语句去迭代生成器,则不会显式的发出StopIteration 异常,而是自动捕获 StopIteration 异常,所以如果遇到 return,只是会终止迭代,而不会触发异常,故而也就没办法获取 return 的值。如下:
def my_generator():
for i in range(5):
if i==2:
return '我被迫中断了'
else:
yield i
def main(generator):
try:
for i in generator: #不会显式触发异常,故而无法获取到return的值
print(i)
except StopIteration as exc:
print(exc.value)
g = my_generator() #调用
main(g)
'''运行结果为:
0
1
'''
for 迭代语句不会显式触发异常,故而无法获取到return 的值,迭代到 2 的时候遇到 return 语句,隐式的触发了 StopIteration 异常,就终止迭代了,但是在程序中不会显示出来。
如果是使用 next()
一次一次迭代,则会显式触发异常,但要获取 return 的返回值,我需要如下操作:
def my_generator():
for i in range(5):
if i==2:
return '我被迫中断了'
else:
yield i
def main(generator):
try:
print(next(generator)) #每次迭代一个值,则会显式出发StopIteration
print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
except StopIteration as exc:
print(exc.value) #获取返回的值
g=my_generator()
main(g)
'''运行结果为:
0
1
我被迫中断了
'''
现在我们使用 yield from
来完成上面的同样的功能:
生成器中return的值 = yield from 生成器对象
def my_generator():
for i in range(5):
if i == 2:
# 遇到return自动触发StopIteration异常
return '我被迫中断了'
else:
yield i
def wrap_my_generator(generator): # 定义一个包装“生成器”的生成器,它的本质还是生成器
result = yield from generator # 自动触发StopIteration异常,并且将return的返回值赋值给yield from表达式的结果,即result
print(result)
def main(generator):
for j in generator:
print(j)
g = my_generator()
wrap_g = wrap_my_generator(g)
main(wrap_g) #调用
'''运行结果为:
0
1
我被迫中断了
'''
从上面的比较可以看出,yield from
具有以下几个特点:
- 单独**
yield
时:的 my_generator 是原始的生成器,main 是调用方,使用 yield 的时候,只涉及到这两个函数,即“调用方”与“生成器(协程函数)”是直接进行交互的,不涉及其他方法,即“调用方——>生成器函数 (协程函数)”;** - 在使用**
yield from
的时:多了一个对原始 my_generator 的包装函数,然后调用方是通过这个包装函数(后面会讲到它专有的名词)来与生成器进行交互的,即“调用方——>生成器包装函数——>生成器函数 (协程函数)”**; yield from iteration
结构会在内部自动捕获 iteration 生成器的 StopIteration 异常。这种处理方式与 for 循环处理 StopIteration 异常的方式一样。而且对yield from
结构来说,解释器不仅会捕获 StopIteration 异常,还会把 return 返回的值或者是 StopIteration 的 value 属性的值变成 yield from 表达式的值,即上面的 result。
委派生成器:包含
yield from
表达式的生成器函数;即上面的 wrap_my_generator生成器包装函数
子生成器:从
yield from
表达式中部分获取的生成器;即上面的 my_generator生成器函数
调用方:调用委派生成器的客户端代码;即上面的main 调用方函数
2. Yield from 的管道传输用法示例
其实 yield from
最重要的作用就是提供了一个“数据传输的管道”,下面通过一个简单的例子加以说明为什么是管道:
def average():
total = 0.0 #数字的总和
count = 0 #数字的个数
avg = None #平均值
while True:
num = yield avg
total += num
count += 1
avg = total/count
def wrap_average(generator):
yield from generator
#定义一个函数,通过这个函数向average函数发送数值
def main(wrap):
print(next(wrap)) #启动生成器
print(wrap.send(10)) # 10
print(wrap.send(20)) # 15
print(wrap.send(30)) # 20
print(wrap.send(40)) # 25
g = average()
wrap = wrap_average(g)
main(wrap)
'''运行结果为:
None
10.0
15.0
20.0
25.0
'''
从上面我们可以发现,调用方发送的数据是发给 wrap_average
的,怎么依然到了生成器函数 average
里面呢?这就是“数据传输管道的作用”。即主函数调用方 main 把各个 value 传给 grouper ,而这个传入的值最终到达 averager 函数中; grouper 并不知道传入的是什么值,因为从上面的代码看出,**wrap_average
里面完全没有处理这个值的任何代码!**
装饰器
函数引用
def test():
print("1")
print(test)
# <function __main__.test>
a = test # 变量A指向函数对象
a() # 变量A可调用函数
什么是闭包
在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这些函数以及用到的一些变量称之为闭包。
闭包的应用
def parse(number: int):
print("第一层函数", number)
def sum(number2):
print("第二层函数加和后", number+number2)
return sum
# 接受函数对象的引用
func = parse(100)
func(100)
# 编写闭包函数
def checkLogin(func):
print("准备检查权限")
def inner():
print("权限正确!")
func() # 执行函数的变量
return inner # 返回一个函数对象
# main变量指向的是mian函数
def main():
print("执行业务1")
# 不用修改主函数的内容,就可以扩展主函数.
main = checkLogin(main) # 此时main变量指向的是inner函数
main()
# 等效于
# 到这里python会运行 checkLogin函数,并把下面的函数名变量指向 checkLogin 函数的返回值,即 inner 函数
@checkLogin
def mian():
print("执行业务2")
# 在调用main之前已经被装饰了
mian()
多个修饰器,从下到上修饰
装饰是倒着装的,执行是顺着执行的。
装饰有参数的函数
对不定长参数的传递
装饰有返回值的参数
通用修饰器
带参数的修饰器
在函数中嵌套闭包函数
Author: WhaleFall
Permalink: https://www.whaleluo.top/python/python-generator-iterator-decorater/
文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。
Comments