1 生成器简介
在Python中构建迭代器必须实现一个带有 iter() 和 next() 方法的类,跟踪内部状态,并在没有返回值时引发 StopIteration。
Python 生成器是创建迭代器的一种简单方法。
简单地说,生成器是一个函数,它返回一个对象(迭代器),可以迭代(一次一个值)。
2 创建生成器
在Python中创建生成器相当简单。它与定义普通函数一样简单,但使用yield语句而不是return语句。
如果一个函数至少包含一条yield语句(它可能包含其他yield或return语句),那么它将成为一个生成器函数。
yield和return都将从函数中返回一些值。不同之处在于,return语句完全终止函数,而yield语句则暂停函数保存其所有状态,然后在后续调用中继续执行。
3 生成器函数和普通函数之间的区别
下面是生成器函数与普通函数的区别:
- 生成器函数包含一个或多个 yield 语句;
- 调用时,它返回一个对象(迭代器),但不会立即开始执行;
- 像 iter() 和 next() 这样的方法是自动实现的。因此,可以使用 next() 遍历这些项;
- 一旦该函数yield,该函数将暂停,并将控制权转移给调用者;
- 局部变量及其状态在连续调用之间会被记住;
- 最后,当函数终止时,StopIteration 会在进一步调用时自动引发。
下面是一个例子来说明上面提到的所有要点。有一个名为 my_gen() 的生成器函数,它有几个 yield 语句。
# A simple generator function
def my_gen():
n = 1
print('This is printed first')
# Generator function contains yield statements
yield n
n += 1
print('This is printed second')
yield n
n += 1
print('This is printed at last')
yield n
运行:
>>> # It returns an object but does not start execution immediately.
>>> a = my_gen()
>>> # We can iterate through the items using next().
>>> next(a)
This is printed first
1
>>> # Once the function yields, the function is paused and the control is transferred to the caller.
>>> # Local variables and theirs states are remembered between successive calls.
>>> next(a)
This is printed second
2
>>> next(a)
This is printed at last
3
>>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration
在上面的例子中有趣的是,在每次调用之间记住变量n 的值。
这与普通函数不同,当生成函数产生时,局部变量不会被销毁。而且,生成器对象只能迭代一次。
要重新启动该进程,需要使用 a = my_gen() 之类的东西创建另一个生成器对象。
最后要注意的是,可以直接使用 for循环 的生成器。
这是因为 for循环 接受一个迭代器,并使用 next()函数 对其进行迭代。当 StopIteration 被引发时,它会自动结束。
# A simple generator function
def my_gen():
n = 1
print('This is printed first')
# Generator function contains yield statements
yield n
n += 1
print('This is printed second')
yield n
n += 1
print('This is printed at last')
yield n
# Using for loop
for item in my_gen():
print(item)
输出:
This is printed first
1
This is printed second
2
This is printed at last
3
4 带有循环的生成器
通常,生成器函数是用一个具有适当终止条件的循环来实现的。
看一个生成器的例子,它可以反转一个字符串
def rev_str(my_str):
length = len(my_str)
for i in range(length - 1, -1, -1):
yield my_str[i]
# For loop to reverse the string
for char in rev_str("hello"):
print(char)
输出:
o
l
l
e
h
在这个例子中,使用了 range()函数 来使用 for循环 逆序获取索引。
注:这个生成器函数不仅可以处理字符串,还可以处理其他类型的可迭代对象,如list、tuple等。
5 生成器表达式
使用生成器表达式可以轻松地动态创建简单的生成器。它使得构建生成器变得容易。
与创建匿名函数的lambda函数类似,生成器表达式也创建匿名生成器函数。
生成器表达式的语法类似于Python中的列表推导式。但是方括号换成了圆括号。
列表推导式和生成器表达式之间的主要区别是:
- 列表推导式生成整个列表,而生成器表达式每次生成一项。
- 生成器表达式具有惰性执行(只在请求时生成项)。
因此,生成器表达式比等效的列表推导式的内存效率高得多。
# Initialize the list
my_list = [1, 3, 6, 10]
# square each term using list comprehension
list_ = [x**2 for x in my_list]
# same thing can be done using a generator expression
# generator expressions are surrounded by parenthesis ()
generator = (x**2 for x in my_list)
print(list_)
print(generator)
输出:
[1, 9, 36, 100]
<generator object <genexpr> at 0x7f5d4eb4bf50>
上面可以看到,生成器表达式并没有立即生成所需的结果。相反,它返回一个生成器对象,该对象只根据需要生成项。
下面是如何从生成器中获取项:
# Initialize the list
my_list = [1, 3, 6, 10]
a = (x**2 for x in my_list)
print(next(a))
print(next(a))
print(next(a))
print(next(a))
next(a)
输出:
1
9
36
100
Traceback (most recent call last):
File "<string>", line 15, in <module>
StopIteration
生成器表达式可以用作函数参数。这样使用时,可以省略圆括号。
>>> sum(x**2 for x in my_list)
146
>>> max(x**2 for x in my_list)
100
6 生成器的使用
生成器如此强大的原因:
1. 容易实现
与迭代器类相比,生成器可以以一种清晰和简洁的方式实现。下面是一个使用迭代器类实现幂次为2的序列的示例:
class PowTwo:
def __init__(self, max=0):
self.n = 0
self.max = max
def __iter__(self):
return self
def __next__(self):
if self.n > self.max:
raise StopIteration
result = 2 ** self.n
self.n += 1
return result
上面的程序冗长且令人困惑。现在,用一个生成器函数来做同样的事情。
def PowTwoGen(max=0):
n = 0
while n < max:
yield 2 ** n
n += 1
因为生成器可以自动跟踪细节,所以实现更加简洁明了。
2. 节约内存
返回序列的普通函数将在返回结果之前在内存中创建整个序列。如果序列中的条目数量非常大,那么这就有点过分了。
这种序列的生成器实现是内存友好的,是首选的,因为它一次只产生一个项。
3. 代表无限数据流
生成器是代表无限数据流的优秀媒介。无限的数据流不能存储在内存中,而且由于生成器每次只能产生一个项,所以它们可以表示无限的数据流。
下面的生成器函数可以生成所有的偶数(至少在理论上)。
def all_even():
n = 0
while True:
yield n
n += 2
4. 管道生成器
可以使用多个生成器对一系列操作进行流水线处理。
假设有一个生成器可以生成斐波那契数列中的数字。我们有另一个平方数生成器。
如果想要求出斐波那契数列中数字的平方和,可以通过将生成器函数的输出流水线化来实现。
def fibonacci_numbers(nums):
x, y = 0, 1
for _ in range(nums):
x, y = y, x+y
yield x
def square(nums):
for num in nums:
yield num**2
print(sum(square(fibonacci_numbers(10))))
输出:
4895
这种流水线是高效的,易于阅读的。
参考:programiz