一篇文章弄懂迭代器、生成器和yield
- 1. 迭代器和生成器
- 1.1 range迭代器和生成器
- 1.2 迭代器
- 1.3 生成器函数和生成器
- 1.4 遍历生成器的三种方式
- 1.5 使用生成器实现惰性求值
- 2. 生成器的next()、send()、throw()、close()
- 3 为什么说send()相当于next(),我们看看底层代码!
- 4. 参考博客
- 5. [[Python] 理解yield关键字、生成器函数和协程](https://blog.csdn/Spade_/article/details/111350083)
1. 迭代器和生成器
迭代器
: Python从可迭代的对象中获取迭代器。如果对象实现了能返回迭代器的__iter__方法,那么对象就是可迭代的。生成器
: 只要Python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。
区别在于,所有生成器都是迭代器,因为生成器完全实现了迭代器接口。但迭代器不是生成器。
1.1 range迭代器和生成器
# 这是一个iterator
>>> mylist = [x * x for x in range(3)]
>>> for ele in mylist:
... print(ele, end=" ")
0 1 4
# 这是一个generator
>>> mylist = (x * x for x in range(3))
>>> for ele in mylist:
... print(ele, end=" ")
0 1 4
1.2 迭代器
迭代器协议:
- 实现__iter__()方法,返回一个迭代器
- 实现__next__()方法,返回当前元素,并指向下一个元素的位置,如果当前位置已无元素,则抛出StopIteration异常。
下面我们使用迭代器协议实现一个斐波那契数列:
# 斐波那契数列
class Fib():
def __init__(self):
self.a = 1
self._b = 1
def __iter__(self): # 不实现__iter__()报错:'Fib' object is not iterable Fib不是可迭代的
return self
def __next__(self): # 不实现__next()__报错:iter() returned non-iterator of type 'Fib' Fib返回了一个不可迭代对象
if self.a > 100:
raise StopIteration("终止")
self.a, self._b = self._b, self.a + self._b
return self.a
# 1. 直接使用for...in...遍历
f = Fib()
for i, a in enumerate(f):
try:
print('return:', a, '\ta: ', f.a, '\tb:', f._b, '\t__next__():', f.__next__())
except StopIteration:
print("==StopIteration==")
# 2. 创建迭代器对象遍历可迭代对象
it = iter(Fib())
for i in it:
print(i, end=" ")
print()
# 3. 使用next()遍历,最后抛出StopIteration
it = iter(Fib())
try:
while True:
print(next(it), end=" ")
except StopIteration:
print("StopIteration")
# dis(Fib) # 查看Python字节码(类似汇编指令的中间语言)
输出结果:
return: 1 a: 1 b: 2 __next__(): 2
return: 3 a: 3 b: 5 __next__(): 5
return: 8 a: 8 b: 13 __next__(): 13
return: 21 a: 21 b: 34 __next__(): 34
return: 55 a: 55 b: 89 __next__(): 89
==StopIteration==
1 2 3 5 8 13 21 34 55 89 144
1 2 3 5 8 13 21 34 55 89 144 StopIteration
1.3 生成器函数和生成器
只要Python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。
下面讲一个例子,demo1()里面使用了yield,是一个生成器函数,返回一个生成器。我们可以使用next()
方法来迭代生成器,每次迭代next()获取到的就是yield产生的值,也就是yield右边的值。
# yield可以理解为暂停按钮,每次执行到yield,保存断点,同时yield还会返回值给调用方
def demo1(value=None):
print('start')
yield 1
yield value
yield 2
print('end')
g = demo1("Value") # 生成器函数也是函数,可以接收传参
print(type(g)) # g是一个生成器
print(next(g)) # 执行yield 1,暂停
print(next(g)) # 执行yield value,暂停
print(next(g)) # 执行yield 2,暂停
print(next(g)) # 找不到yield了,raise StopIteration
<class 'generator'>
start
1
Value
2
end
Traceback (most recent call last):
File "D:/Desktop/Projects/效率编程/协程/test.py", line 14, in <module>
print(next(g)) # 找不到yield了,raise StopIteration
StopIteration
总共三次yield,我们是调用方,使用next(g)收到了三个yield返回值,当我们尝试调用第四次时,demo1函数产生了一个StopIteration
告诉我们找不到yield了,raise StopIteration。
看一个使用生成器实现斐波那契数列的例子:
def fibonacci(n): # 生成器函数 - 斐波那契
a, b, counter = 0, 1, 0
while True:
if (counter > n):
return
yield a # TODO:每次执行到yield就暂停
a, b = b, a + b
counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
while True:
try:
print (next(f), end=" ")
except StopIteration:
print("StopIteration")
break
# print(dis(fibonacci)) # 查看Python字节码(类似汇编指令的中间语言)
print(type(f)) # <class 'generator'> 即类型为生成器对象
print(dir(f)) # 这个对象实现了__iter__()和__next__()
输出结果:
0 1 1 2 3 5 8 13 21 34 55 StopIteration
<class 'generator'>
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
1.4 遍历生成器的三种方式
可以看到生成器也实现了__iter__()和__next__(),所以可以使用和迭代器一样的遍历方法。
- for … in … 遍历
- iter() 遍历
- list() 转为list
>>> f = fibonacci(10)
>>> for i in f:
... print(i, end=" ")
...
0 1 1 2 3 5 8 13 21 34 55 >>>
>>> f = fibonacci(10)
>>> for i in iter(f):
... print(i, end=" ")
...
0 1 1 2 3 5 8 13 21 34 55
>>> f = fibonacci(10)
>>> list(f)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
1.5 使用生成器实现惰性求值
生成器的迭代体验实际上同迭代器完全相同,但其与迭代器的区别在于生成器中的元素只能被循环一次。这是因为生成器不会在内存中存储全部的元素,而是动态地产生下一个将被循环的元素。
使用生成器我们可以实现惰性求值,按行读取大文件之类时,不需要一次将大文件加载到内存,而是需要时,再取出文件的行:[Python] 生成器按行读取大文件
另外,Python内置了很多优秀的生成器,感兴趣可以参考文档: itertools。或者参考《Python Cookbook》,里面介绍了itertools常用的函数。
2. 生成器的next()、send()、throw()、close()
直接上代码!
def echo(value=None):
print("当next()被调用时,第一次执行开始")
try:
while True:
try:
value = (yield value) # 第一次next():yield value之后暂停,赋值不会执行。第二次next():赋值yield给value,yield默认为None
except Exception as e:
value = e
finally:
print("当close()被调用时,别忘了清理")
# 每一个生成器函数被调用之后,它的函数体并不执行
generator = echo(1)
# 当第一次调用next()时,函数开始执行,执行到yield表达式为止
# value = (yield value)只是执行了yield value这个表达式
print(next(generator)) # 1
# 当第二次调用next()时,yield表达式的值复制给了value
# 而yield表达式默认“返回值”就是"None",所以此时value的值就是None
print(next(generator)) # None
# yield有一个返回值,send(value)的作用就是控制yield的返回值value
# next(generator)相当于是generator.send(None)
print(generator.send(2)) # 2
print(generator.send(None)) # None
# yield也可以抛出异常
print(generator.throw(TypeError, "Error")) # Error
# 调用close(),yield会抛出GeneratorExit异常并且自行处理
generator.close()
# 调用close()之后,对象不再可用,next()或send()会抛出StopIteration异常
next(generator)
# generator.send(None)
输出结果:
当next()被调用时,第一次执行开始
1
None
2
None
Error
当close()被调用时,别忘了清理
Traceback (most recent call last):
File "E:/Documents/PythonCode/yield.py", line 35, in <module>
next(generator)
StopIteration
3 为什么说send()相当于next(),我们看看底层代码!
Python底层是由C语言实现的,让我们摘出next()和send()来观察看看!
static PyObject *
gen_iternext(PyGenObject *gen)
{
return gen_send_ex(gen, NULL, 0); # 传入NULL
}
static PyObject *
gen_send(PyGenObject *gen, PyObject *arg)
{
return gen_send_ex(gen, arg, 0); # 传入可变参数
}
从上面的代码中可以看到,send和next都是调用的同一函数gen_send_ex,区别在于是否带有参数。
gen_send_ex()参考:https://wwwblogs/coder2012/p/4990834.html
4. 参考博客
Python中的yield关键字:https://www.jianshu/p/fb67382a0455
Python yield与实现:https://wwwblogs/coder2012/p/4990834.html
菜鸟教程迭代器和生成器:https://www.runoob/python3/python3-iterator-generator.html
5. [Python] 理解yield关键字、生成器函数和协程
更多推荐
[Python] 一篇文章弄懂迭代器、生成器和yield
发布评论