Python生成器 (Generators in Python)
Generators in Python
文章目录
Introduction 导言
生成器generator是 Python 中用来生成迭代器Iterators的一个方便而强大的工具。本篇文章将通过一些示例来解释和深入介绍 Python 中的生成器generators。
如果您还没有完全理解 Itreators,不用担心,请阅读此篇文章。
贯穿全文的几句话
-
只要一个函数function中使用了
yield
这个关键字,就代表这个函数function每次调用时返回的是一个生成器对象 generator object。这个生成器对象的类型是<class ‘generator’>。 -
包含
yield
语句的函数function本身并不是生成器generator,它仍然是一个函数function。生成器generator是一个类class,而不是函数function。 -
生成器generator是迭代器Iterator的一个子类subclass。
-
生成器generator保存的是产生item的生成方法/算法,而不是items。
-
next()
函数只能用于生成器generator类型。不能用于函数function。
def func():
yield "Hello"
print(func) # <function func at 0x10d55c0d0>
print(type(func)) # <class 'function'>
g1 = func()
g2 = func()
print(id(g1), id(g2)) # 4519738272 4519739168
print(g1) # <generator object func at 0x10d65bba0>
print(type(g1)) # <class 'generator'>
print(next(g1)) # Hello
为什么 Python 有生成器Generator?
我们可以通过在 Python 类class中实现implementing __iter__()
和 __next__()
特殊方法special methods来获得迭代器Iterator。不过,这种方法有点复杂,尽管它有助于理解迭代器Iterators的真正工作原理。
通过生成器generators创建迭代器Iterators是一种更好、更方便的方法。事实上,生成器就是迭代器的子类the Generator is a subclass of the Iterator。
Iterable
可迭代对象、Iterator
迭代器 和 Generator
生成器 的关系如下:
如上图所示,Iterator 是 Iterable 的子类,Generator 是 Iterator 的子类。
# 源码在_collections_abc.py
class Iterable(metaclass=ABCMeta):
@abstractmethod
def __iter__(self): ...
# 源码在_collections_abc.py
class Iterator(Iterable):
@abstractmethod
def __next__(self):
raise StopIteration
def __iter__(self):
return self
# 源码在_collections_abc.py
class Generator(Iterator):
def __iter__(self):
return self
def __next__(self):
"""Return the next item from the generator.
When exhausted, raise StopIteration.
"""
return self.send(None)
@abstractmethod
def send(self, value):
"""Send a value into the generator.
Return next yielded value or raise StopIteration.
"""
raise StopIteration
@abstractmethod
def throw(self, typ, val=None, tb=None):
"""Raise an exception in the generator.
Return next yielded value or raise StopIteration.
"""
...
def close(self):
"""Raise GeneratorExit inside generator.
"""
...
生成器(Generator)与迭代器(Iterator)具有相同的作用,用于保存一个知道如何生成所需元素的方法method。在Python中操作一个大的列表是非常耗时的。如果我们每次只需要获取一个元素element,那么生成器generator就是一个很好的选择,它可以减少时间和空间成本。
在 Python 中,只要一个函数function中使用了 yield
这个关键字,就代表这个函数function每次调用时都是返回一个生成器对象 generator object,注意:包含 yield
语句的函数function本身并不是生成器generator,它仍然是一个函数function。生成器generator是一个类class,而不是函数function。而 yield
的作用就相当于让 Python 帮我们把一个“串行”的逻辑转换成 iterator 的形式。
生成器generator都是Iterator迭代器对象。
如何获得生成器Generator?
1. 生成器表达式 Generator Expression
生成器表达式generator expression是获取生成器generator的最简单方法。它与 列表推导式list comprehensions 非常相似。我们只需将括号brackets改为小括号parentheses。
my_list = [i for i in range(8)]
my_generator = (i for i in range(8))
print(my_list)
print(my_generator)
# [0, 1, 2, 3, 4, 5, 6, 7]
# <generator object <genexpr> at 0x7f8fc3ec9a40>
由于生成器generator保存的是item生成方法而不是items,因此我们需要使用 next()
函数逐个获取项目get items one by one,这与迭代器Iterator相同。当所有项目items都生成后, next()
函数将引发 StopIteration
错误信息。当然,我们也可以使用 for 循环来获取生成器generator中的项目items。
2. 使用yield定义生成器Generator
如果一个函数function包含 yield
语句,它就可以产生生成器generators。
def my_generator(maximum):
n = 0
while n < maximum:
n += 1
yield n
return 'Done'
g = my_generator(maximum=5)
print(g) # <generator object my_generator at 0x10e269ba0>
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3
print(next(g)) # 4
print(next(g)) # 5
next(g)
# Traceback (most recent call last):
# File "/usr/lib/python3.9/code.py", line 15, in <module>
# next(g)
# StopIteration: Done
yield
表示 “产生”或“生成”produce。当程序执行到 yield
语句时,就会 "生产produce"一个值即项目item,而 next()
函数function就会在此暂停pauses there执行,等待下一次调用。
当我们再次使用 next()
函数function对生成器对象generator object进行调用,它会让生成器对象generator object从上一次暂停的位置继续执行,直到遇到下一个 yield
语句或者执行结束。
普通函数normal functions 与 包含 yield
的函数functions including yield 的主要区别在于执行流程execution flow:
- 普通函数按顺序执行executes sequentially,并在遇到
return
语句statement或到达最后一行final line时返回结果。 - 包括
yield
的函数会在调用next()
时执行,并在遇到yield
语句时返回。再次调用next()
时,将从上次暂停的yield
语句处继续执行。
有一个例子:
def example():
print('step 1')
yield 1
print('step 2')
yield 2
print('step 3')
yield 3
g = example()
next(g)
# step 1
# 1
next(g)
# step 2
# 2
next(g)
# step 3
# 3
next(g)
# Traceback (most recent call last):
# File "/usr/lib/python3.9/code.py", line 21, in <module>
# next(g)
# StopIteration
注:包含 yield
语句的函数本身并不是生成器generator。它仍然是一个函数function,但每次调用这个函数function时都可以返回一个生成器对象return a generator,这个生成器对象的类型是<class ‘generator’>。生成器generator是一个类class,而不是函数function。(正如我们之前所说,生成器generator是迭代器Iterator的一个子类subclass)。
next()
只能用于生成器generator类型。不能用于函数function。
def my_generator(maximum):
n = 0
while n < maximum:
yield n
return 'Done'
print(type(my_generator)) # <class 'function'>
print(type(my_generator(5))) # <class 'generator'>
print(my_generator(5)) # <generator object my_generator at 0x10bc42ba0>
print(next(my_generator(5))) # 0
print(next(my_generator))
# Traceback (most recent call last):
# File "/usr/lib/python3.9/code.py", line 15, in <module>
# print(next(my_generator))
# TypeError: 'function' object is not an iterator
更多Generator应用实例
到目前为止,我们知道生成器generators可以帮助我们保存生成项目items的算法,并在需要时生成项目items。与包含所有项目items的庞大列表list相比,生成器可以减少时间和内存成本。
表示无限的数据流infinite stream of data
事实上,生成器generator甚至可以表示无限的数据流infinite stream of data。例如:
def fibonacci():
x, y = 0, 1
while True:
x, y = y, x + y
yield x
fib = fibonacci()
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))
print(next(fib))
# ...
fib
是一个无限生成器infinite generator,我们可以根据自己的需要使用它。
将多个生成器generators组成管道pipeline
生成器generators的另一个有趣应用interesting application是,我们可以将一系列生成器generators组合起来,得到一个新的生成器generator,这在技术technically上被称为 “管道pipeline”。
def times_two(nums):
for n in nums:
yield n * 2
def natural_number(maximum):
x = 0
while x < maximum:
yield x
x += 1
p = times_two(natural_number(10))
print(type(p)) # <class 'generator'>
print(next(p)) # 0
print(next(p)) # 2
print(next(p)) # 4
print(next(p)) # 6
print(next(p)) # 8
print(next(p)) # 10
print(next(p)) # 12
# ...
如上例所示,我们可以使用现有的两个生成器generators来定义一个新的生成器generator。这不是很好吗?
Conclusion 结论
生成器Generator是 Python 中一种非常有用的机制useful mechanism,可以减少时间reduce time和内存开销memory costs。它保存的是产生项item的算法algorithm而不是项items。我们还可以使用生成器generators生成produce无限的数据流infinite data stream和管道pipelines。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!