|
|
|
|
移动端

2.2.2 yield语句

《Python高级编程(第2版)》第2章语法最佳实践——类级别以下,本章将介绍现在这门语言的语法中最重要的元素,以及它们的使用技巧。本节为大家介绍yield语句。

作者:张亮/阿信 译来源:人民邮电出版社|2018-01-29 18:24

技术沙龙 | 6月30日与多位专家探讨技术高速发展下如何应对运维新挑战!


2.2.2 yield语句

生成器提供了一种优雅的方法,可以让编写返回元素序列的函数所需的代码变得简单、高效。基于yield语句,生成器可以暂停函数并返回一个中间结果。该函数会保存执行上下文,稍后在必要时可以恢复。

举个例子,斐波纳契(Fibonacci)数列可以用生成器语法来实现。下列代码是来自于PEP 255(简单生成器)文档中的例子:

  1. def fibonacci():  
  2.     a, b = 0, 1  
  3.     while True:  
  4.         yield b  
  5.         a, bb = b, a + b 

你可以用next()函数或for循环从生成器中获取新的元素,就像迭代器一样:

  1. >>> fib = fibonacci()  
  2. >>> next(fib)  
  3. 1  
  4. >>> next(fib)  
  5. 1  
  6. >>> next(fib)  
  7. 2  
  8. >>> [next(fib) for i in range(10)]  
  9. [3, 5, 8, 13, 21, 34, 55, 89, 144, 233] 

这个函数返回一个generator对象,是特殊的迭代器,它知道如何保存执行上下文。它可以被无限次调用,每次都会生成序列的下一个元素。这种语法很简洁,算法可无限调用的性质并没有影响代码的可读性。不必提供使函数停止的方法。实际上,它看上去就像用伪代码设计的数列一样。

在社区中,生成器并不常用,因为开发人员还不习惯这种思考方式。多年来,开发人员已经习惯于使用直截了当的函数。每次你需要返回一个序列的函数或在循环中运行的函数时,都应该考虑使用生成器。当序列元素被传递到另一个函数中以进行后续处理时,一次返回一个元素可以提高整体性能。

在这种情况下,用于处理一个元素的资源通常不如用于整个过程的资源重要。因此,它们可以保持位于底层,使程序更加高效。举个例子,斐波那契数列是无穷的,但用来生成它的生成器每次提供一个值,并不需要无限大的内存。一个常见的应用场景是使用生成器的数据流缓冲区。使用这些数据的第三方代码可以暂停、恢复和停止生成器,在开始这一过程之前无需导入所有数据。

举个例子,来自标准库的tokenize模块可以从文本流中生成令牌(token),并对处理过的每一行都返回一个迭代器,以供后续处理:

  1. >>> import tokenize  
  2. >>> reader = open('hello.py').readline  
  3. >>> tokens = tokenize.generate_tokens(reader)  
  4. >>> next(tokens)  
  5. TokenInfo(type=57 (COMMENT), string='# -*- coding: utf-8 -*-'start=(1,  
  6. 0), end=(1, 23), line='# -*- coding: utf-8 -*-\n')  
  7. >>> next(tokens)  
  8. TokenInfo(type=58 (NL), string='\n'start=(1, 23), end=(1, 24), line='#  
  9. -*- coding: utf-8 -*-\n')  
  10. >>> next(tokens)  
  11. TokenInfo(type=1 (NAME), string='def'start=(2, 0), end=(2, 3),  
  12. line='def hello_world():\n'

从这里可以看出,open遍历文件的每一行,而generate _ tokens则利用管道对其进行遍历,完成一些额外的工作。对于基于某些序列的数据转换算法而言,生成器还有助于降低算法复杂度并提高效率。把每个序列看作一个iterator,然后再将其合并为一个高阶函数,这种方法可以有效避免函数变得庞大、丑陋、没有可读性。此外,这种方法还可以为整个处理链提供实时反馈。

在下面的示例中,每个函数都定义了一个对序列的转换。然后将这些函数链接起来并应用。每次调用都将处理一个元素并返回其结果:

  1. def power(values):  
  2.     for value in values:  
  3.         print('powering %s' % value)  
  4.         yield value  
  5. def adder(values):  
  6.     for value in values:  
  7.         print('adding to %s' % value)  
  8.         if value % 2 == 0:  
  9.             yield value + 3  
  10.         else:  
  11.             yield value + 2 

将这些生成器合并使用,可能的结果如下:

  1. >>> elements = [1, 4, 7, 9, 12, 19]  
  2. >>> results = adder(power(elements))  
  3. >>> next(results)  
  4. powering 1  
  5. adding to 1  
  6. 3  
  7. >>> next(results)  
  8. powering 4  
  9. adding to 4  
  10. 7  
  11. >>> next(results)  
  12. powering 7  
  13. adding to 7  
  14. 9  

保持代码简单,而不是保持数据简单  

最好编写多个处理序列值的简单可迭代函数,而不要编写一个复杂函数,同时计算出整个集合的结果。

Python生成器的另一个重要特性,就是能够利用next函数与调用的代码进行交互。yield变成了一个表达式,而值可以通过名为send的新方法来传递:

  1. def psychologist():  
  2.        print('Please tell me your problems')  
  3.        while True:  
  4.            answer = (yield)  
  5.            if answer is not None:  
  6.                if answer.endswith('?'):  
  7.                    print("Don't ask yourself too much questions")  
  8.                elif 'good' in answer:  
  9.                    print("Ahh that's good, go on")  
  10.                elif 'bad' in answer:  
  11.                    print("Don't be so negative") 

下面是调用psychologist()函数的示例会话:

  1. >>> free = psychologist()  
  2. >>> next(free)  
  3. Please tell me your problems  
  4. >>> free.send('I feel bad')  
  5. Don't be so negative  
  6. >>> free.send("Why I shouldn't ?")  
  7. Don't ask yourself too much questions  
  8. >>> free.send("ok then i should find what is good for me")  
  9. Ahh that's good, go on 

send的作用和next类似,但会将函数定义内部传入的值变成yield的返回值。因此,这个函数可以根据客户端代码来改变自身行为。为完成这一行为,还添加了另外两个函数:throw和close。它们将向生成器抛出错误。

throw:允许客户端代码发送要抛出的任何类型的异常。

close:作用相同,但会引发特定的异常——GeneratorExit。在这种情况下,生成器函数必须再次引发GeneratorExit或StopIteration。

生成器是Python中协程、异步并发等其他概念的基础,这些概念将在第13章介绍。

喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:365934973

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

Java程序设计专家门诊

精选目前国内外最流行的程序设计语言——Java作为本书的选题,并以丰富的内容来解决读者学习该语言时可能遇到的各种问题。以专业的论坛为基...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊