AI智能
改变未来

Python中的生成器

生成器:generator

Python生成器简介

在本文中,我们将学习如何使用 Python 生成器轻松地创建迭代器、生成器与迭代器和普通函数有何不同,以及为什么应该使用生成器。

在 Python 中构建迭代器(

iterator

)需要做大量的工作。我们必须使用

_iter_()

_next_()

方法实现一个类,跟踪内部状态,并在没有返回值时引发

StopIteration

异常。

这既冗长又违反直觉,在这种情况下,生成器(

generator

)可以拯救世界了。Python 生成器是创建迭代器的一种简单方法。上面提到的所有工作都是由生成器自动处理的,也就是说自动实现了

_iter_()

_ next _ ()

方法。

简单来说,生成器是一个返回对象(迭代器)的函数,我们可以对其进行迭代(每次迭代一个值)。

Python生成器的创建

用 Python 创建生成器相当简单。这就像定义一个普通函数一样简单,但是使用

yield

语句而不是

return

语句。

如果一个函数包含至少一个 yield 语句(它可能包含其他

yield

语句或

return

语句) ,它就成为一个生成器函数。yield 和 return 都会从函数返回一些值。

不同之处在于,当 return 语句完全终止一个函数时,yield 语句暂停保存其所有状态的函数,并在以后的连续调用中继续执行。

生成器 VS 函数

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

  • 生成器函数包含一个或多个 yield 语句。
  • 生成器函数在调用时,返回一个对象(迭代器) ,但不会立即开始执行。
  • 生成器函数自动实现迭代器的两个魔法方法,因此可以使用next()迭代这些项。
  • 生成器函数一旦创建,函数将暂停并将控制转移到调用方。
  • 生成器函数的局部变量及其状态在连续调用之间被记住。
  • 当生成器函数终止时,在进一步调用时自动引发 StopIteration。

这里有一个例子来说明上述所有要点。我们有一个名为 my_gen() 的生成器函数,它包含几个 yield 语句。

# 一个简单的生成器函数def my_gen():n = 1print(\'第一次打印\')# 生成器函数包含yield语句yield nn += 1print(\'第二次打印\')yield nn += 1print(\'最后打印\')yield n

运行结果:

# 返回一个生成器,但不会立即执行a = my_gen()# 使用next()函数进行迭代操作next(a)# 输出:第一次打印# 输出:1# 一旦生成器函数yield,函数会暂停并将控制转移到调用方# 局部变量及其状态在连续调用之间被记住next(a)# 输出:第二次打印# 输出:2next(a)# 输出:最后打印# 输出:3# 最终,函数终止。后续如果继续调用则会引发StopIteration异常。next(a)  # 将引起 StopIteration 异常

在上面的例子中需要注意的一件有趣的事情是,变量 n 的值在每次调用之间都会被记住。

与普通函数不同,当函数 yield 时,局部变量不会被破坏。此外,生成器对象只能迭代一次。要重新启动进程,我们需要使用类似

a = my_gen()

这样的代码创建另一个生成器对象。

最后要注意的是,我们可以直接使用 for 循环的生成器。

这是因为 for 循环接受一个迭代器,并使用

next()

函数对其进行迭代。当

StopIteration

被引发时,它自动结束。

如需了解在 Python 中 for 循环实际是如何实现的,请参阅“python迭代器”一文。

示例:

# 一个简单的生成器函数def my_gen():n = 1print(\'第一次打印\')# 生成器函数包含yield语句yield nn += 1print(\'第二次打印\')yield nn += 1print(\'最后打印\')yield n# 使用for循环遍历for item in my_gen():print(item)

当你运行这个程序时,输出结果会是:

第一次打印1第二次打印2最后打印3

带循环的Python生成器

上面的例子用处不大,我们研究它只是为了搞清楚背后发生了什么。通常,生成器函数是通过具有适当终止条件的回路来实现的。

让我们再来看一个生成器的例子,它用来反转字符串。

# 生成器函数def rev_str(my_str):length = len(my_str)for i in range(length - 1, -1, -1):yield my_str[i]# 用来反转字符串的for循环for char in rev_str("hello"):print(char)

执行后输出:

olleh

在这个例子中,我们使用

range()

函数给 for 循环提供逆序的索引值。

注意: 这个生成器函数不仅可以处理字符串,还可以处理列表、元组等其他类型的可迭代对象。

Python 生成器表达式

使用生成器表达式,可以很容易地动态创建简单的生成器,这使得构建生成器变得容易。

与创建匿名函数的 lambda 函数类似,生成器表达式创建匿名生成器函数。

生成器表达式的语法是:

(item for item in iterable if condition)

类似于 Python 中的列表推导式,不同的是列表推导式使用方括号

[ ]

,而生成器使用圆括号

( )

代替。列表推导式和生成器表达式的主要区别在于,列表推导式生成整个列表,而生成器表达式生成一个项。

并且,生成器是延迟执行(只有在被要求的时候才生产项)。由于这个原因,生成器表达式比等效的列表推导式具有更高的内存效率。

# 初始化列表my_list = [1, 3, 6, 10]# 使用列表推导式将每一项进行平方list_ = [x**2 for x in my_list]# 使用生成器可以实现同样的效果generator = (x**2 for x in my_list)print(list_)print(generator)

输出:

[1, 9, 36, 100]<generator object <genexpr> at 0x7f5d4eb4bf50>

我们可以在上面看到,生成器表达式没有立即生成所需的结果。相反,它返回一个生成器对象,该对象仅按需生成项。

以下是我们如何开始从生成器获取元素项的方法:

# 初始化列表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)

当我们运行上面的程序时,我们得到以下输出:

1936100Traceback (most recent call last):File "<string>", line 15, in <module>StopIteration

生成器表达式可以作为函数参数使用。当以这种方式使用时,圆括号可以被删除。

sum(x**2 for x in my_list)  # 输出:146max(x**2 for x in my_list)  # 输出:100

使用 Python 生成器

有几个原因使生成器成为一个强大的实现。

  1. 易于实现

与迭代器类相比,生成器可以以简洁明了的方式实现。下面是一个使用迭代器类实现2次幂序列的示例。

这是迭代器的代码量:

class PowTwo:def __init__(self, max=0):self.n = 0self.max = maxdef __iter__(self):return selfdef __next__(self):if self.n > self.max:raise StopIterationresult = 2 ** self.nself.n += 1return result

上面的程序很长而且令人抓狂。现在,让我们用一个生成器函数来做同样的事情。

这是生成器的代码(没有对比就没有伤害😂)

def PowTwoGen(max=0):n = 0while n < max:yield 2 ** nn += 1

由于生成器(generator)自动跟踪细节,实现起来简洁明了。

  1. 内存效率

返回序列的普通函数将在返回结果之前,在内存中创建整个序列。如果在序列中的项目数量非常大,对内存的消耗非常大。

相反,这种序列的生成器实现则对内存友好,因为它一次只生成一个项目,所以是首选的。

  1. 表示无限流

生成器是表示无限数据流的优秀媒介。无限流不能存储在内存中,而且由于生成器一次只生成一个项,因此它们可以表示无限的数据流。

下面的生成器函数可以生成所有的偶数(至少在理论上)。

def all_even():n = 0while True:yield nn += 2
  1. 管道生成器

多个生成器可用于管道一系列的操作。这是最好的说明使用一个例子。

假设我们有一个生成器,它生成

Fibonacci

斐波拉契数列中的数字。我们还有另一个平方数的生成器。

如果我们要求出斐波拉契数列中数字的平方和,我们可以通过以下方法,将生成器函数的输出结合在一起来实现。

# 斐波拉契数列生成器def fibonacci_numbers(nums):x, y = 0, 1for _ in range(nums):x, y = y, x+yyield x# 平方和生成器def square(nums):for num in nums:yield num**2print(sum(square(fibonacci_numbers(10))))

执行后输出:

4895

这种流水线操作非常有效,并且易于阅读。

是的,简直酷到没朋友!

—END

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Python中的生成器