1. 首页
  2. 技术文章

40、Python生成器

在本教程中,您将学习如何使用Python生成器轻松创建迭代,它与迭代器和常规函数有何不同,以及为什么要使用它。

Python中的生成器

在Python中构建迭代器有很多工作。我们必须使用__iter__()and__next__()方法实现一个类,跟踪内部状态,并StopIteration在没有值要返回时引发。

这既冗长又违反直觉。在这种情况下,发电机可以进行救援。

Python生成器是创建迭代器的一种简单方法。我们上面提到的所有工作都由Python的生成器自动处理。

简而言之,生成器是一个函数,它返回一个对象(迭代器),我们可以对其进行迭代(一次一个值)。


用Python创建生成器

在Python中创建生成器非常简单。它与定义普通函数一样简单,但是使用一条yield语句而不是一条return语句。

如果一个函数至少包含一个yield语句(它可能包含另一个yield或多个return语句),则它将成为生成器函数。两者yieldreturn都会从函数中返回一些值。

不同之处在于,尽管一条return语句完全终止了一个函数,但yield语句暂停了该函数并保存了其所有状态,然后在后续调用中从那里继续执行。


生成器功能与常规功能之间的区别

这是生成器功能与常规功能的不同之处。

  • 生成器函数包含一个或多个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

在上面的示例中要注意的一件事是变量的值 ñ 在每次通话之间被记住。

与普通函数不同,局部变量在函数屈服时不会被破坏。此外,生成器对象只能被迭代一次。

要重新启动该过程,我们需要使用来创建另一个生成器对象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循环以相反的顺序获取索引。

注意:此生成器函数不仅适用于字符串,还适用于其他种类的可迭代对象,例如listtuple等。


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]
<生成器对象<genexpr>位于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

这种流水线高效且易于阅读(是的,非常酷!)。

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站不拥有所有权,不承担相关法律责任。如发现有侵权/违规的内容, 联系QQ1841324605,本站将立刻清除。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

联系我们

服务热线:130-0886-1890

QR code