Python yield的使用详解

目录
  • 如何生成斐波那契數列
  • return 的作用
  • 另一个例子
  • 总结

如何生成斐波那契數列

斐波那契(Fibonacci)數列是一个非常简单的递归数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到。用计算机程序输出斐波那契數列的前 N 个数是一个非常简单的问题,许多初学者都可以轻易写出如下函数:

清单 1. 简单输出斐波那契數列前 N 个数

实例

#!/usr/bin/python
# -*- coding: UTF-8 -*-
def fab(max):
    n, a, b = 0, 0, 1
    while n < max:
        print b
        a, b = b, a + b
        n = n + 1
fab(5)

执行以上代码,我们可以得到如下输出:

1
1
2
3
5

结果没有问题,但有经验的开发者会指出,直接在 fab 函数中用 print 打印数字会导致该函数可复用性较差,因为 fab 函数返回 None,其他函数无法获得该函数生成的数列。

要提高 fab 函数的可复用性,最好不要直接打印出数列,而是返回一个 List。以下是 fab 函数改写后的第二个版本:

清单 2. 输出斐波那契數列前 N 个数第二版

实例

#!/usr/bin/python
# -*- coding: UTF-8 -*-
def fab(max):
    n, a, b = 0, 0, 1
    L = []
    while n < max:
        L.append(b)
        a, b = b, a + b
        n = n + 1
    return L
for n in fab(5):
    print n

可以使用如下方式打印出 fab 函数返回的 List:

1
1
2
3
5

改写后的 fab 函数通过返回 List 能满足复用性的要求,但是更有经验的开发者会指出,该函数在运行中占用的内存会随着参数 max 的增大而增大,如果要控制内存占用,最好不要用 List

来保存中间结果,而是通过 iterable 对象来迭代。例如,在 Python2.x 中,代码:

清单 3. 通过 iterable 对象来迭代

for i in range(1000): pass

会导致生成一个 1000 个元素的 List,而代码:

for i in xrange(1000): pass

则不会生成一个 1000 个元素的 List,而是在每次迭代中返回下一个数值,内存空间占用很小。因为 xrange 不返回 List,而是返回一个 iterable 对象。

利用 iterable 我们可以把 fab 函数改写为一个支持 iterable 的 class,以下是第三个版本的 Fab:

清单 4. 第三个版本

实例

#!/usr/bin/python
# -*- coding: UTF-8 -*-
class Fab(object):
    def __init__(self, max):
        self.max = max
        self.n, self.a, self.b = 0, 0, 1
    def __iter__(self):
        return self
    def next(self):
        if self.n < self.max:
            r = self.b
            self.a, self.b = self.b, self.a + self.b
            self.n = self.n + 1
            return r
        raise StopIteration()
for n in Fab(5):
    print n

Fab 类通过 next() 不断返回数列的下一个数,内存占用始终为常数:

1
1
2
3
5

然而,使用 class 改写的这个版本,代码远远没有第一版的 fab 函数来得简洁。如果我们想要保持第一版 fab 函数的简洁性,同时又要获得 iterable 的效果,yield 就派上用场了:

清单 5. 使用 yield 的第四版

实例

#!/usr/bin/python
# -*- coding: UTF-8 -*-
def fab(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b      # 使用 yield
        # print b
        a, b = b, a + b
        n = n + 1
for n in fab(5):
    print n

第四个版本的 fab 和第一版相比,仅仅把 print b 改为了 yield b,就在保持简洁性的同时获得了 iterable 的效果。

调用第四版的 fab 和第二版的 fab 完全一致:

1
1
2
3
5

简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。

也可以手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 fab 的执行流程:

清单 6. 执行流程

>>>f = fab(5)
>>> f.next()
1
>>> f.next()
1
>>> f.next()
2
>>> f.next()
3
>>> f.next()
5
>>> f.next()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteration

当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。

我们可以得出以下结论:

一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。

如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:

清单 7. 使用 isgeneratorfunction 判断

>>>from inspect import isgeneratorfunction
>>> isgeneratorfunction(fab)
True

要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别:

清单 8. 类的定义和类的实例

>>>import types
>>> isinstance(fab, types.GeneratorType)
False
>>> isinstance(fab(5), types.GeneratorType)
True

fab 是无法迭代的,而 fab(5) 是可迭代的:

>>>from collections import Iterable
>>> isinstance(fab, Iterable)
False
>>> isinstance(fab(5), Iterable)
True

每次调用 fab 函数都会生成一个新的 generator 实例,各实例互不影响:

>>>f1 = fab(3)
>>> f2 = fab(5)
>>> print 'f1:', f1.next()
f1: 1
>>> print 'f2:', f2.next()
f2: 1
>>> print 'f1:', f1.next()
f1: 1
>>> print 'f2:', f2.next()
f2: 1
>>> print 'f1:', f1.next()
f1: 2
>>> print 'f2:', f2.next()
f2: 2
>>> print 'f2:', f2.next()
f2: 3
>>> print 'f2:', f2.next()
f2: 5

return 的作用

在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。

另一个例子

另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:

清单 9. 另一个 yield 的例子

实例

def read_file(fpath):
    BLOCK_SIZE = 1024
    with open(fpath, 'rb') as f:
        while True:
            block = f.read(BLOCK_SIZE)
            if block:
                yield block
            else:
                return

以上仅仅简单介绍了 yield 的基本概念和用法,yield 在 Python 3 中还有更强大的用法,我们会在后续文章中讨论。

注:本文的代码均在 Python 2.7 中调试通过

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 通过实例简单了解python yield使用方法

    实例代码如下 def demo(): print("开始执行...") while 1: res = yield '这是返回值' print("res:",res) d1 = demo() print(d1) print(next(d1)) print("*"*20) print(next(d1)) 执行结果如下: 开始执行... 这是返回值 ******************** res: None 这是返回值 代码解释,个人理解,说得不对希

  • python使用yield压平嵌套字典的超简单方法

    我们经常遇到各种字典套字典的数据,例如: nest_dict = { 'a': 1, 'b': { 'c': 2, 'd': 3, 'e': {'f': 4} }, 'g': {'h': 5}, 'i': 6, 'j': {'k': {'l': {'m': 8}}} } 有没有什么简单的办法,把它压扁,变成: { 'a': 1, 'b_c': 2, 'b_d': 3, 'b_e_f': 4, 'g_h': 5, 'i': 6, 'j_k_l_m': 8 } 你肯定想到了使用递归来解决这个问题,那

  • Python yield 使用方法浅析

    如何生成斐波那契數列 斐波那契(Fibonacci)數列是一个非常简单的递归数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到.用计算机程序输出斐波那契數列的前 N 个数是一个非常简单的问题,许多初学者都可以轻易写出如下函数: 清单 1. 简单输出斐波那契數列前 N 个数 def fab(max): n, a, b = 0, 0, 1 while n < max: print b a, b = b, a + b n = n + 1 执行 fab(5),我们可以得到如下输出: >>

  • python如何正确使用yield

    生成器 如果在一个方法内,包含了 yield 关键字,那么这个函数就是一个「生成器」. 生成器其实就是一个特殊的迭代器,它可以像迭代器那样,迭代输出方法内的每个元素. 我们来看一个包含 yield 关键字的方法: # coding: utf8 # 生成器 def gen(n):     for i in range(n):         yield i g = gen(5)      # 创建一个生成器 print(g)        # <generator object gen at 0x

  • 在Python中如何使用yield

    一.生成器 如果在一个方法内,包含了 yield 关键字,那么这个函数就是一个「生成器」. 生成器其实就是一个特殊的迭代器,它可以像迭代器那样,迭代输出方法内的每个元素. 我们来看一个包含 yield 关键字的方法: # coding: utf8 # 生成器 def gen(n): for i in range(n): yield i g = gen(5) # 创建一个生成器 print(g) # <generator object gen at 0x10bb46f50> print(type

  • Python yield的使用详解

    目录 如何生成斐波那契數列 return 的作用 另一个例子 总结 如何生成斐波那契數列 斐波那契(Fibonacci)數列是一个非常简单的递归数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到.用计算机程序输出斐波那契數列的前 N 个数是一个非常简单的问题,许多初学者都可以轻易写出如下函数: 清单 1. 简单输出斐波那契數列前 N 个数 实例 #!/usr/bin/python # -*- coding: UTF-8 -*- def fab(max): n, a, b = 0, 0,

  • python函数式编程学习之yield表达式形式详解

    前言 yield的英文单词意思是生产,刚接触Python的时候感到非常困惑,一直没弄明白yield的用法.最近又重新学习了下,所以整理了下面这篇文章,供自己和大家学习参考,下面话不多说了,来一起看看详细的介绍吧. 先来看一个例子 def foo(): print("starting...") while True: res = yield print("res:",res) g = foo() next(g) 在上面的例子里,因为foo函数中有yield关键字,所以

  • python中yield的用法详解——最简单,最清晰的解释

    首先我要吐槽一下,看程序的过程中遇见了yield这个关键字,然后百度的时候,发现没有一个能简单的让我懂的,讲起来真TM的都是头头是道,什么参数,什么传递的,还口口声声说自己的教程是最简单的,最浅显易懂的,我就想问没有有考虑过读者的感受. 接下来是正题: 首先,如果你还没有对yield有个初步分认识,那么你先把yield看做"return",这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值,返回之后程序就不再往下运行了.看做return之后再把它

  • 对python中return与yield的区别详解

    首先比较下return 与 yield的区别: return:在程序函数中返回某个值,返回之后函数不在继续执行,彻底结束. yield: 带有yield的函数是一个迭代器,函数返回某个值时,会停留在某个位置,返回函数值后,会在前面停留的位置继续执行,直到程序结束 首先,如果你还没有对yield有个初步分认识,那么你先把yield看做"return",这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值,返回之后程序就不再往下运行了.看做return

  • python中yield的用法详解

    首先我要吐槽一下,看程序的过程中遇见了yield这个关键字,然后百度的时候,发现没有一个能简单的让我懂的,讲起来真TM的都是头头是道,什么参数,什么传递的,还口口声声说自己的教程是最简单的,最浅显易懂的,我就想问没有有考虑过读者的感受. 接下来是正题: 首先,如果你还没有对yield有个初步分认识,那么你先把yield看做"return",这个是直观的,它首先是个return,普通的return是什么意思,就是在程序中返回某个值,返回之后程序就不再往下运行了.看做return之后再把它

  • 对Python 内建函数和保留字详解

    保留字: In [4]: import keyword In [5]: keyword.kwlist Out[5]: ['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda'

  • Python学习之迭代器详解

    目录 什么是迭代器 如何生成迭代器 迭代器函数 - iter() 函数 与 next() 函数 可迭代的对象 生成迭代器 迭代器的用法 - 演示案例 什么是迭代器 迭代是 python 中访问集合元素的一种非常强大的一种方式.迭代器是一个可以记住遍历位置的对象,因此不会像列表那样一次性全部生成,而是可以等到用的时候才生成,因此节省了大量的内存资源.迭代器对象从集合中的第一个元素开始访问,直到所有的元素被访问完.迭代器有两个方法:iter()和 next()方法. 这么解释可能不太直观,我们以生活

  • MySQL数据库设计之利用Python操作Schema方法详解

    弓在箭要射出之前,低声对箭说道,"你的自由是我的".Schema如箭,弓似Python,选择Python,是Schema最大的自由.而自由应是一个能使自己变得更好的机会. Schema是什么? 不管我们做什么应用,只要和用户输入打交道,就有一个原则--永远不要相信用户的输入数据.意味着我们要对用户输入进行严格的验证,web开发时一般输入数据都以JSON形式发送到后端API,API要对输入数据做验证.一般我都是加很多判断,各种if,导致代码很丑陋,能不能有一种方式比较优雅的验证用户数据呢

  • Python之str操作方法(详解)

    1. str.format():使用"{}"占位符格式化字符串(占位符中的索引号形式和键值对形式可以混合使用). >>> string = 'python{}, django{}, tornado{}'.format(2.7, 'web', 'tornado') # 有多少个{}占位符就有多少个值与其对应,按照顺序"填"进字符串中 >>> string 'python2.7, djangoweb, tornadotornado'

  • 基于python时间处理方法(详解)

    在处理数据和进行机器学习的时候,遇到了大量需要处理的时间序列.比如说:数据库读取的str和time的转化,还有time的差值计算.总结一下python的时间处理方面的内容. 一.字符串和时间序列的转化 time.strptime():字符串=>时间序列 time.strftime():时间序列=>字符串 import time start = "2017-01-01" end = "2017-8-12" startTime = time.strptime

随机推荐