上一章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。
# 一个简单的生成器函数
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))
这个管道操作非常有效,并且易于阅读,这要方便得多!
推荐阅读
- python3闭包closure – Python3教程
- python3迭代器iterator – Python3教程
- 六大算法设计技巧(贪婪法、分治法、动态规划、随机化算法、回溯法和分支限界法)
- python3运算符重载 – Python3教程
- python3多重继承 – Python3教程
- python3继承 – Python3教程
- python3对象和类 – Python3教程
- python3面向对象编程 – Python3教程
- java|程序员加班实用贴 | 每日趣闻