Python 高手编程系列七百一十六:yield 语句

生成器提供了一种优雅的方法,可以让编写返回元素序列的函数所需的代码变得简单、
高效。基于 yield 语句,生成器可以暂停函数并返回一个中间结果。该函数会保存执行上
下文,稍后在必要时可以恢复。
举个例子,斐波纳契(Fibonacci)数列可以用生成器语法来实现。下列代码是来自于
PEP 255(简单生成器)文档中的例子:
def fibonacci():
a, b = 0, 1
while True:
yield b
a, b = b, a + b
你可以用 next()函数或 for 循环从生成器中获取新的元素,就像迭代器一样:

fib = fibonacci()
next(fib)
1
next(fib)
1
next(fib)
2
[next(fib) for i in range(10)]
[3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
这个函数返回一个 generator 对象,是特殊的迭代器,它知道如何保存执行上下文。
它可以被无限次调用,每次都会生成序列的下一个元素。这种语法很简洁,算法可无限调
用的性质并没有影响代码的可读性。不必提供使函数停止的方法。实际上,它看上去就像
用伪代码设计的数列一样。
在社区中,生成器并不常用,因为开发人员还不习惯这种思考方式。多年来,开发人员已经习惯于使用直截了当的函数。每次你需要返回一个序列的函数或在循环中运行的函
数时,都应该考虑使用生成器。当序列元素被传递到另一个函数中以进行后续处理时,一
次返回一个元素可以提高整体性能。
在这种情况下,用于处理一个元素的资源通常不如用于整个过程的资源重要。因此,
它们可以保持位于底层,使程序更加高效。举个例子,斐波那契数列是无穷的,但用来生
成它的生成器每次提供一个值,并不需要无限大的内存。一个常见的应用场景是使用生成
器的数据流缓冲区。使用这些数据的第三方代码可以暂停、恢复和停止生成器,在开始这
一过程之前无需导入所有数据。
举个例子,来自标准库的 tokenize 模块可以从文本流中生成令牌(token),并对处
理过的每一行都返回一个迭代器,以供后续处理:
import tokenize
reader = open(‘hello.py’).readline
tokens = tokenize.generate_ tokens(reader)
next(tokens)
TokenInfo(type=57 (COMMENT), string=‘# -- coding: utf-8 --’, start=(1,
0), end=(1, 23), line=‘# -- coding: utf-8 --\n’)
next(tokens)
TokenInfo(type=58 (NL), string=‘\n’, start=(1, 23), end=(1, 24), line=‘#
-- coding: utf-8 --\n’)
next(tokens)
TokenInfo(type=1 (NAME), string=‘def’, start=(2, 0), end=(2, 3),
line='def hello
_world():\n’)
从这里可以看出,open 遍历文件的每一行,而 generate_tokens 则利用管道对其
进行遍历,完成一些额外的工作。对于基于某些序列的数据转换算法而言,生成器还有助
于降低算法复杂度并提高效率。把每个序列看作一个 iterator,然后再将其合并为一个
高阶函数,这种方法可以有效避免函数变得庞大、丑陋、没有可读性。此外,这种方法还
可以为整个处理链提供实时反馈。
在下面的示例中,每个函数都定义了一个对序列的转换。然后将这些函数链接起来并
应用。每次调用都将处理一个元素并返回其结果:
def power(values):
for value in values:
print(‘powering %s’ % value)
yield value
def adder(values):
for value in values:
print(‘adding to %s’ % value)
if value % 2 == 0:
yield value + 3
else:
yield value + 2
将这些生成器合并使用,可能的结果如下:
elements = [1, 4, 7, 9, 12, 19]
results = adder(power(elements))
next(results)
powering 1
adding to 1
3
next(results)
powering 4
adding to 4
7
next(results)
powering 7
adding to 7
9
Python 生成器的另一个重要特性,就是能够利用 next 函数与调用的代码进行交互。
yield 变成了一个表达式,而值可以通过名为 send 的新方法来传递:
def psychologist():
print(‘Please tell me your problems’)
while True:
answer = (yield)
if answer is not None:
if answer.endswith(‘?’):
print(“Don’t ask yourself too much questions”)
elif ‘good’ in answer:
print(“Ahh that’s good, go on”)
elif ‘bad’ in answer:
print(“Don’t be so negative”)
下面是调用 psychologist()函数的示例会话:
free = psychologist()
next(free)
Please tell me your problems
free.send(‘I feel bad’)
Don’t be so negative
free.send(“Why I shouldn’t ?”)
Don’t ask yourself too much questions
free.send(“ok then i should find what is good for me”)
Ahh that’s good, go on
send 的作用和 next 类似,但会将函数定义内部传入的值变成 yield 的返回值。因
此,这个函数可以根据客户端代码来改变自身行为。为完成这一行为,还添加了另外两个
函数:throw 和 close。它们将向生成器抛出错误。
• throw:允许客户端代码发送要抛出的任何类型的异常。
• close:作用相同,但会引发特定的异常 — GeneratorExit。在这种情况下,
生成器函数必须再次引发 GeneratorExit 或 StopIteration。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值