python3生成器generator – Python3教程

上一章Python教程请查看:python3迭代器iterator
在本文中,你将了解如何使用Python生成器轻松地创建迭代,它与迭代器和普通函数有何不同,以及为什么应该使用它。
Python中的生成器是什么?在Python中构建迭代器有很多开销,我们必须使用__iter__()和_next__()方法来实现一个类,跟踪内部状态,在没有要返回的值时触发StopIteration等等。
【python3生成器generator – Python3教程】这既冗长又违反直觉,生成器在这种情况下可以派上用场。
Python生成器是创建迭代器的一种简单方法,上面提到的所有开销都由Python中的生成器自动处理。
简单地说,生成器是一个函数,它返回一个对象(迭代器),我们可以迭代该对象(一次一个值)。
如何在Python中创建生成器?用Python创建生成器相当简单,它与用yield语句而不是return语句定义普通函数一样简单。
如果一个函数包含至少一个yield语句(它可能包含其他yield或return语句),它就成为一个生成器函数,yield和return都将从函数返回一些值。
不同之处在于,当return语句完全终止一个函数时,yield语句会暂停该函数保存其所有状态,然后在后续调用时继续执行。
生成器函数与普通函数的区别以下是生成器函数与普通函数的区别。

  • 生成器函数包含一个或多个yield语句。
  • 当被调用时,它会返回一个对象(迭代器),但不会立即开始执行。
  • 像__iter__()和_next__()这样的方法是自动实现的,因此,我们可以使用next()遍历这些项。
  • 一旦函数产生结果,函数就会暂停,控制就会转移给调用者。
  • 局部变量及其状态在连续调用之间被记住。
  • 最后,当函数终止时,在进一步调用时将自动引发StopIteration。
这里有一个例子来说明上面提到的所有要点,我们有一个名为my_gen()的生成器函数,其中包含几个yield语句。
# 一个简单的生成器函数 def my_gen(): n = 1 print('首先打印出来') # 生成器函数包含yield语句 yield nn += 1 print('这是第二次打印') yield nn += 1 print('终于打印出来了') yield n

下面给出了解释器中的交互式运行,在Python shell中运行这些代码以查看输出。
>>> # 它返回一个对象,但不立即开始执行。 >>> a = my_gen() >>> # 我们可以使用next()迭代这些项。 >>> next(a) 首先打印出来 1 >>> # 一旦函数产生结果,函数就会暂停,控制就会转移给调用者。 >>> # 局部变量及其状态在连续调用之间被记住。 >>> next(a) 这是第二次打印 2 >>> next(a) 终于打印出来了 3 >>> # 最后,当函数终止时,在进一步调用时将自动引发StopIteration。 >>> next(a) Traceback (most recent call last): ... StopIteration >>> next(a) Traceback (most recent call last): ... StopIteration

在上面的例子中需要注意的一件有趣的事情是,变量n的值在每次调用之间都会被记住。
与普通函数不同,局部变量在函数产生时不会被破坏,此外,生成器对象只能迭代一次。
为了重新启动这个过程,我们需要使用类似于a = my_gen()的方法来创建另一个生成器对象。
注意:最后要注意的是,我们可以直接将生成器与for循环一起使用。
这是因为,for循环接受一个迭代器,并使用next()函数对其进行迭代,当StopIteration被触发时,它会自动结束。
# 一个简单的生成器函数 def my_gen(): n = 1 print('首先打印出来') # 生成器函数包含yield语句 yield nn += 1 print('这是第二次打印') yield nn += 1 print('终于打印出来了') yield n# 使用for循环 for item in my_gen(): print(item)

带有循环的Python生成器上面的例子用处不大,我们研究它只是为了了解背景中发生了什么。
通常,生成器函数是通过具有适当终止条件的循环来实现的。
让我们以反转字符串的生成器为例。
def rev_str(my_str): length = len(my_str) for i in range(length - 1,-1,-1): yield my_str[i]# For循环来反转字符串 # 输出: # o # l # l # e # h for char in rev_str("hello"): print(char)

在本例中,我们使用range()函数使用for循环以相反的顺序获取索引。
事实证明,这个生成器函数不仅可以处理字符串,还可以处理其他类型的迭代器,如列表、元组等。
Python生成器表达式使用生成器表达式可以方便地动态创建简单的生成器,它使建造生成器变得容易。
与lambda函数创建匿名函数一样,生成器表达式创建匿名生成器函数。
生成器表达式的语法类似于Python中的列表理解,但是方括号被圆括号代替了。
列表理解和生成器表达式之间的主要区别是,列表理解生成整个列表,而生成器表达式一次生成一个项。
他们有点懒惰,只在被要求时才生产条目,由于这个原因,生成器表达式比等价的列表理解更有效。
# 初始化列表 my_list = [1, 3, 6, 10]# 输出: [1, 9, 36, 100] [x**2 for x in my_list]# 同样的事情可以使用生成器表达式来完成 # 输出: < generator object < genexpr> at 0x0000000002EBDAF8> (x**2 for x in my_list)

我们可以看到上面的生成器表达式并没有立即生成所需的结果。相反,它返回一个生成器对象,该对象根据需要生成项目。
# 初始化列表 my_list = [1, 3, 6, 10]a = (x**2 for x in my_list) # 输出: 1 print(next(a))# 输出: 9 print(next(a))# 输出: 36 print(next(a))# 输出: 100 print(next(a))# 输出: StopIteration next(a)

生成器表达式可以在函数中使用,当以这种方式使用时,圆括号可以去掉。
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100

为什么在Python中使用生成器?有几个原因使生成器成为一个有吸引力的实现。
1. 容易实现
与它们的迭代器类对应项相比,生成器可以以一种清晰而简洁的方式实现。下面是一个使用iterator类实现2的幂序列的例子。
class PowTwo: def __init__(self, max = 0): self.max = max def __iter__(self): self.n = 0 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. 管道生成器
生成器可用于输送一系列操作,这可以用一个例子来最好地说明。
假设我们有一个来自著名快餐连锁店的日志文件。日志文件中有一列(第4列)记录每小时售出的披萨数量,我们希望将其加起来计算出5年内售出的披萨总数。
假设所有内容都是字符串,不可用的数字被标记为’ N/A’ 。它的生成器实现可以如下所示。
with open('sells.log') as file: pizza_col = (line[3] for line in file) per_hour = (int(x) for x in pizza_col if x != 'N/A') print("总卖出披萨 = ",sum(per_hour))

这个管道操作非常有效,并且易于阅读,这要方便得多!

    推荐阅读