📜  Python生成器

📅  最后修改于: 2020-09-19 13:54:19             🧑  作者: Mango

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

Python的生成器

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

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

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

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

用Python创建生成器

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

如果一个函数至少包含一个yield语句(它可能包含其他yieldreturn语句),则它将成为生成器函数。 yieldreturn都将从函数返回一些值。

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

生成器函数与常规函数之间的区别

这是生成器函数与常规函数的不同之处。

  1. 生成器函数包含一个或多个yield语句。
  2. 调用时,它返回一个对象(迭代器),但不会立即开始执行。
  3. 诸如__iter__()__next__()是自动实现的。因此,我们可以使用next()遍历所有项目。
  4. 一旦函数屈服, 函数将暂停并将控件转移到调用方。
  5. 连续调用之间会记住局部变量及其状态。
  6. 最后,当函数终止时,在进一步的调用中将自动引发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

带有循环的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循环以相反的顺序获取索引。

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

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]
 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

使用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

上面的程序冗长而令人困惑。现在,让我们使用generator 函数进行相同的操作。

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

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