Python yield 使用浅析

您可能听说过,带有 yield 的函数在 python 中被称之为 generator(生成器),何谓 generator ?

  我们先抛开 generator,以一个常见的编程题目来展示 yield 的概念。

  如何生成斐波那契數列

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

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

立即学习“Python免费学习笔记(深入)”;

deffab(max):   n, a, b =0, 0, 1   whilen 

  执行 fab(5),我们可以得到如下输出:

>>> fab(5)

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

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

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

deffab(max):   n, a, b =0, 0, 1   L =[]   whilen 

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

>>> forn infab(5):

...     printn

...

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

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

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

fori inrange(1000): pass

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

fori inxrange(1000): pass

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

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

  清单 4. 第三个版本

classFab(object):       def__init__(self, max):       self.max=max       self.n, self.a, self.b =0, 0, 1       def__iter__(self):       returnself       defnext(self):       ifself.n 

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

>>> forn inFab(5):

...     printn

...

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

  清单 5. 使用 yield 的第四版

deffab(max):    n, a, b =0, 0, 1    whilen 

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

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

>>> forn infab(5):

...     printn

...

  简单地讲,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()

>>> f.next()

>>> f.next()

>>> f.next()

>>> f.next()

>>> f.next()

Traceback (most recent call last):

File"", line 1, in StopIteration

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

  我们可以得出以下结论:

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

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

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

  清单 7. 使用 isgeneratorfunction 判断

>>> frominspect importisgeneratorfunction

>>> isgeneratorfunction(fab)

True

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

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

>>> importtypes

>>> isinstance(fab, types.GeneratorType)

False

>>> isinstance(fab(5), types.GeneratorType)

True

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

>>> fromcollections importIterable

>>> 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 的例子

defread_file(fpath):   BLOCK_SIZE =1024   with open(fpath, 'rb') as f:       whileTrue:           block =f.read(BLOCK_SIZE)           ifblock:               yieldblock           else:               return

登录后复制

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2283634.html

(0)
上一篇 2025年2月27日 20:16:47
下一篇 2025年2月19日 02:30:19

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 利用Python的SocketServer框架编写网络服务程序

    1.前言: 虽说用Python编写简单的网络程序很方便,但复杂一点的网络程序还是用现成的框架比较好。这样就可以专心事务逻辑,而不是套接字的各种细节。SocketServer模块简化了编写网络服务程序的任务。同时SocketServer模块也…

    编程技术 2025年2月27日
    200
  • python数据类型转换

    python类型转换 函数                      描述   int(x [,base ])         将x转换为一个整数   long(x [,base ])        将x转换为一个长整数   float(x…

    编程技术 2025年2月27日
    200
  • Python的字符串索引和分片

    1.字符串的索引 给出一个字符串,可输出任意一个字符,如果索引为负数,就是相当于从后向前数。 >>> str=”HelloWorld!” >>> print str[0] H 立即…

    编程技术 2025年2月27日
    200
  • python None与Null 的区别

    一、python对象 要理解这个,首先要理解Python对象: python对象具有三个特性:身份、类型、值。 三特性在对象创建时被赋值。只有值可以改变,其他只读。 类型本身也是对象。 立即学习“Python免费学习笔记(深入)”; 二、N…

    编程技术 2025年2月27日
    200
  • Python2和Python3的10大区别

    1.性能 Py3.0运行pystone benchmark的速度比Py2.5慢30%。Guido认为Py3.0有极大的优化空间,在字符串和整形操作上可以取得很好的优化结果。 2.编码 Py3.0源码文件默认使用utf-8编码,这就使得以下代…

    编程技术 2025年2月27日
    200
  • python全局变量

    全局变量不符合参数传递的精神,所以,平时我很少使用,除非定义常量。今天有同事问一个关于全局变量的问题,才发现其中原来还有门道。 程序大致是这样的: CONSTANT = 0def modifyConstant() :        prin…

    编程技术 2025年2月27日
    200
  • python 图 自身遍历及弱引用使用

    在【python 标准库】中看到的一段代码,非常有帮助: def all_nodes(self):        yield self        n = self.other        while n and n.name != s…

    编程技术 2025年2月27日
    200
  • python图像文字识别

    最近在想没有一个图片文字识别的工具呢?我想到了ocr,国内比较牛逼的汉王ocr。那借助python能否实现呢?于是我找啊找查啊查有关python在这方面探讨的资料,发现pytesser 这样一个好玩的程序!拿出来分享讨论一下: PyTess…

    编程技术 2025年2月27日
    200
  • 深入理解python函数传参机制

    首先需要申明的一点是,python里是没有像c和c++里那样按值传参的说法的。python中的所有东西都是对象,这也是它的强大之处,它没有基本类型之说。 在python中,类型属于对象,变量是没有类型的,这正是python的语言特性,也是吸…

    编程技术 2025年2月27日
    200
  • Python命名空间实例解析

    python的命名空间是python程序猿必须了解的内容,对python命名空间的学习,将使我们在本质上掌握一些python中的琐碎的规则。 接下来我将分四部分揭示Python命名空间的本质:一、命名空间的定义;二、命名空间的查找顺序;三、…

    编程技术 2025年2月27日
    200

发表回复

登录后才能评论