python中的生成器

1 生成器简介

在Python中构建迭代器必须实现一个带有 iter() 和 next() 方法的,跟踪内部状态,并在没有返回值时引发 StopIteration。

Python 生成器是创建迭代器的一种简单方法。

简单地说,生成器是一个函数,它返回一个对象(迭代器),可以迭代(一次一个值)。

2 创建生成器

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

如果一个函数至少包含一条yield语句(它可能包含其他yield或return语句),那么它将成为一个生成器函数。

yield和return都将从函数中返回一些值。不同之处在于,return语句完全终止函数,而yield语句则暂停函数保存其所有状态,然后在后续调用中继续执行。

3 生成器函数和普通函数之间的区别

下面是生成器函数与普通函数的区别:

  1. 生成器函数包含一个或多个 yield 语句;
  2. 调用时,它返回一个对象(迭代器),但不会立即开始执行
  3. iter() 和 next() 这样的方法是自动实现的。因此,可以使用 next() 遍历这些项;
  4. 一旦该函数yield,该函数将暂停,并将控制权转移给调用者;
  5. 局部变量及其状态在连续调用之间会被记住;
  6. 最后,当函数终止时,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中的列表推导式。但是方括号换成了圆括号

列表推导式和生成器表达式之间的主要区别是:

  1. 列表推导式生成整个列表,而生成器表达式每次生成一项。
  2. 生成器表达式具有惰性执行(只在请求时生成项)。

因此,生成器表达式比等效的列表推导式的内存效率高得多。

# 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

上一篇:2065 "红色病毒"问题


下一篇:A - 你能数的清吗