Python 生成器
Python 生成器
在本教程中,您将学习如何使用 Python 生成器轻松创建迭代,它与迭代器和普通函数有何不同,以及为什么要使用它。
视频:Python 生成器
Python 中的生成器
在 Python 中构建迭代器需要做很多工作。我们必须用 __iter__()
实现一个类 和 __next__()
方法,跟踪内部状态,并引发 StopIteration
当没有要返回的值时。
这既冗长又违反直觉。在这种情况下,生成器会来救援。
Python 生成器是一种创建迭代器的简单方法。我们上面提到的所有工作都是由 Python 中的生成器自动处理的。
简单来说,生成器是一个函数,它返回一个我们可以迭代的对象(迭代器)(一次一个值)。
在 Python 中创建生成器
在 Python 中创建生成器相当简单。就像定义一个普通函数一样简单,但是使用 yield
语句而不是 return
声明。
如果一个函数至少包含一个 yield
语句(它可能包含其他 yield
或 return
语句),它成为一个生成器函数。 yield
和 return
将从函数中返回一些值。
不同的是,虽然 return
语句完全终止一个函数,yield
语句暂停函数保存其所有状态,然后在后续调用中从那里继续。
Generator函数和Normal函数的区别
以下是生成器函数与普通函数的不同之处。
- 生成器函数包含一个或多个
yield
声明。 - 调用时,它返回一个对象(迭代器),但不会立即开始执行。
__iter__()
等方法 和__next__()
是自动实现的。所以我们可以使用next()
遍历项目 .- 一旦函数屈服,函数就会暂停,并将控制权转移给调用者。
- 在连续调用之间会记住局部变量及其状态。
- 最后,当函数终止时,
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
下面给出了解释器中的交互式运行。在 Python shell 中运行这些以查看输出。
>>> # 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
时自动结束 被提出。查看此处了解 Python 中 for 循环的实际实现方式。
# 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
带循环的 Python 生成器
上面的例子用处不大,我们研究它只是为了了解背景中发生的事情。
通常,生成器函数是通过具有适当终止条件的循环来实现的。
让我们以一个反转字符串的生成器为例。
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循环以相反顺序获取索引的函数。
注意 :这个生成器函数不仅适用于字符串,还适用于其他类型的可迭代对象,如列表、元组等。
Python 生成器表达式
使用生成器表达式可以轻松地动态创建简单的生成器。它使构建生成器变得容易。
与创建匿名函数的 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
Python 生成器的使用
有几个原因使生成器成为强大的实现。
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
这种流水线高效且易于阅读(是的,更酷!)。
Python