📅  最后修改于: 2020-09-19 13:54:19             🧑  作者: Mango
在Python中构建迭代器有很多工作。我们必须使用__iter__()
和__next__()
方法实现一个类,跟踪内部状态,并在没有值要返回的情况下提高StopIteration
。
这既冗长又违反直觉。在这种情况下,发电机可以进行救援。
Python生成器是创建迭代器的简单方法。我们上面提到的所有工作都由Python的生成器自动处理。
简而言之,生成器是一个函数 ,它返回一个对象(迭代器),我们可以对其进行迭代(一次一个值)。
在Python创建生成器非常简单。它与定义普通函数一样简单,但是使用yield
语句而不是return
语句。
如果一个函数至少包含一个yield
语句(它可能包含其他yield
或return
语句),则它将成为生成器函数。 yield
和return
都将从函数返回一些值。
不同之处在于,尽管return
语句完全终止了函数 ,而yield
语句暂停了该函数保存了所有状态,然后在后续调用中从那里继续执行。
这是生成器函数与常规函数的不同之处。
yield
语句。 __iter__()
和__next__()
是自动实现的。因此,我们可以使用next()
遍历所有项目。 StopIteration
。 这是一个示例,用于说明上述所有要点。我们有一个名为my_gen()
的生成器函数, 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()
类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
上面的例子用处不大,我们研究它只是为了了解背景中发生的事情。
通常,生成器功能通过具有合适终止条件的循环来实现。
让我们以反转字符串的生成器为例。
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等。
使用生成器表达式可以轻松地动态创建简单的生成器。它使建造发电机变得容易。
类似于创建匿名函数的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]
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 "", line 15, in
StopIteration
生成器表达式可以用作函数参数。以这种方式使用时,可以删除圆括号。
>>> sum(x**2 for x in my_list)
146
>>> max(x**2 for x in my_list)
100
有多种原因使生成器成为强大的实现。
与生成器类的生成器相比,生成器可以以简洁明了的方式实现。以下是使用迭代器类实现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
上面的程序冗长而令人困惑。现在,让我们使用generator 函数进行相同的操作。
def PowTwoGen(max=0):
n = 0
while n < max:
yield 2 ** n
n += 1
由于生成器自动跟踪细节,因此实现简洁明了。
一个普通的返回序列的函数会在返回结果之前在内存中创建整个序列。如果序列中的项目数量很大,这是一个过大的杀伤力。
这种序列的生成器实现是内存友好的,因此是首选的,因为它一次只能生成一项。
生成器是代表无限数据流的绝佳媒介。无限流不能存储在内存中,并且由于生成器一次只生成一项,因此它们可以表示无限数据流。
以下生成器函数可以生成所有偶数(至少在理论上)。
def all_even():
n = 0
while True:
yield n
n += 2
可以使用多个生成器来流水线化一系列操作。最好用一个例子说明。
假设我们有一个生成斐波那契数列中数字的生成器。我们还有另一个生成平方的生成器。
如果我们想找出斐波那契数列中数字的平方和,可以通过以下方式将生成器函数的输出流水线化来实现。
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
这种流水线高效且易于阅读(是的,非常酷!)。