Python generator生成器和yield表达式详解


Posted in Python onAugust 08, 2019

前言

Python生成器(generator)并不是一个晦涩难懂的概念。相比于MetaClass和Closure等概念,其较为容易理解和掌握。但相对于程序结构:顺序、循环和分支而言其又不是特别的直观。无论学习任何的东西,概念都是非常重要的。正确树立并掌握一些基础的概念是灵活和合理运用的前提,本文将以一种通俗易懂的方式介绍一下generator和yield表达式。

1. Iterator与Iterable

首先明白两点:

  • Iterator(迭代器)是可迭代对象;
  • 可迭代对象并不一定是Iterator;

比较常见的数据类型list、tuple、dict等都是可迭代的,属于collections.Iterable类型;

迭代器不仅可迭代还可以被内置函数next调用,属于collections.Iterator类型;

迭代器是特殊的可迭代对象,是可迭代对象的一个子集。

将要介绍的gererator(生成器)是types.GeneratorType类型,也是collections.Iterator类型。

也就是说生成器是迭代器,可被next调用,也可迭代。

三者的包含关系:(可迭代(迭代器(生成器)))

  • 迭代器:可用next()函数访问的对象;
  • 生成器:生成器表达式和生成器函数;

2. Python生成器

python有两种类型的生成器:生成器表达式和生成器函数。

由于生成器可迭代并且是iterator,因此可以通过for和next进行遍历。

2.1 生成器表达式

把列表生成式的[]改成()便得到生成器表达式。

>>> gen = (i + i for i in xrange(10))
>>> gen
<generator object <genexpr> at 0x0000000003A2DAB0>
>>> type(gen)
<type 'generator'>
>>> isinstance(gen, types.GeneratorType) and isinstance(gen, collections.Iterator) and isinstance(gen, collections.Iterable)
True
>>>

2.2 生成器函数

python函数定义中有关键字yield,该函数便是一个生成器函数,函数调用返回的是一个generator.

def yield_func():
  for i in xrange(3):
    yield i
gen_func = yield_func()
for yield_val in gen_func:
  print yield_val

生成器函数每次执行到yield便会返回,但与普通函数不同的是yield返回时会保留当前函数的执行状态,再次被调用时可以从中断的地方继续执行。

2.3 next与send

通过for和next可以遍历生成器,而send则可以用于向生成器函数发送消息。

def yield_func():
  for i in xrange(1, 3):
    x = yield i
    print 'yield_func',x
gen_func = yield_func()
print 'iter result: %d' % next(gen_func)
print 'iter result: %d' % gen_func.send(100)

结果:

iter result: 1
yield_func 100
iter result: 2

简单分析一下执行过程:

  • line_no 5 调用生成器函数yield_func得到函数生成器gen_func;
  • line_no 6 使用next调用gen_func,此时才真正的开始执行yield_func定义的代码;
  • line_no 3 执行到yield i,函数yield_func暂停执行并返回当前i的值1.
  • line_no 6 next(gen_func)得到函数yield_func执行到yield i返回的值1,输出结果iter result: 1;
  • line_no 7 执行gen_func.send(100);
  • line_no 3 函数yield_func继续执行,并将调用者send的值100赋值给x;
  • line_no 4 输出调用者send接收到的值;
  • line_no 3 执行到yield i,函数yield_func暂停执行并返回当前i的值2.
  • line_no 7 执行gen_func.send(100)得到函数yield_func运行到yield i返回的值2,输出结果iter result: 2;

如果在上面代码后面再加一行:

print 'iter result: %d' % next(gen_func)

结果:

iter result: 1
yield_func 100
iter result: 2
yield_func None
File "G:\Cnblogs\Alpha Panda\Main.py", line 22, in <module>
  print 'iter result: %d' % next(gen_func)
StopIteration

yield_func只会产生2个yield,但是我们迭代调用了3次,会抛出异常StopIteration。

next和send均会触发生成器函数的执行,使用for遍历生成器函数时不要用send。原因后面解释。

2.4 生成器返回值

使用了yield的函数严格来讲已经不是一个函数,而是一个生成器。因此函数中yield和return是不能同时出现的。

SyntaxError: 'return' with argument inside generator

生成器只能通过yield将每次调用的结果返回给调用者。

2.5 可迭代对象转成迭代器

list、tuple、dict等可迭代但不是迭代器的对象可通过内置函数iter转化为iterator,便可以通过next进行遍历;

这样的好处是可以统一使用next遍历所有的可迭代对象;

tup = (1,2,3)
for ele in tup:
  print ele + ele

上面的代码等价于:

tup_iterator = iter(tup)while True:
  try:
    ele = next(tup_iterator)
  except StopIteration:
    break
  print ele + ele

for循环使用next遍历一个迭代器,混合使用send可能会导致混乱的遍历流程。

其实到这里生成器相关的概念基本已经介绍完成了,自己动手过一遍应该能弄明白了。为了更加深刻的体会生成器,下面我们在往前走一步。

3. range与xrange

在Python 2中这两个比较常用,看一下两者的区别:

  • range为一个内置函数,xrange是一个类;
  • 前者返回一个list,后者返回一个可迭代对象;
  • 后者遍历操作快于前者,且占用更少内存;

这里xrange有点类似于上面介绍的生成器表达式,虽然xrange返回的并不是生成器,但两者均返回并不包含全部结果可迭代对象。

3.1 自定义xrange的Iterator版本

作为一个iterator:

The iterator objects themselves are required to support the following two methods, which together form the iterator protocol:

iterator.__iter__()
Return the iterator object itself. This is required to allow both containers and iterators to be used with the for and in statements. This method corresponds to the tp_iter slot of the type structure for Python objects in the Python/C API.

iterator.next()
Return the next item from the container. If there are no further items, raise the StopIteration exception. This method corresponds to the tp_iternext slot of the type structure for Python objects in the Python/C API.

下面我们自定义class my_xrange:

class my_xrange(object):
  def __init__(self, start, stop = None, step = 1):
    """ 仅仅为了演示,假设start, stop 和 step 均为正整数 """
    self._start = 0 if stop is None else start
    self._stop = start if stop is None else stop
    self._step = step
    self._cur_val = self._start

  def __iter__(self):
    return self
  def next(self):
    if self._start <= self._cur_val < self._stop:
      cur_val = self._cur_val
      self._cur_val += self._step
      return cur_val
    raise StopIteration

测试结果:

import collections
myxrange = my_xrange(0, 10, 3)
res = []
for val in myxrange:
  res.append(val)
print res == range(0, 10, 3) # True
print isinstance(myxrange, collections.Iterator)
# Trueprint isinstance(myxrange, types.GeneratorType)
# False

3.2 使用函数生成器

下面使用函数生成器定义一个generator版的xrange。

def xrange_func(start, stop, step = 1):
  """ 仅仅为了演示,假设start, stop 和 step 均为正整数 """
  cur_val = start
  while start <= cur_val and cur_val < stop:
    yield cur_val
    cur_val += step
isinstance(myxrange, collections.Iterator) and isinstance(myxrange, types.GeneratorType) is True

上面两个自定义xrange版本的例子,均说明生成器以及迭代器保留数列生成过程的状态,每次只计算一个值并返回。这样只要占用很少的内存即可表示一个很大的序列。

4. 应用

不管是迭代器还是生成器,对于有大量有规律的数据产生并需要遍历访问的情景均适用,占用内存少而且遍历的速度快。其中一个较为经典的应用为斐波那契数列(Fibonacci sequence)。

这里以os.walk遍历目录为例来说明yield的应用。如果我们需要遍历一个根目录下的所有文件并根据需要进行增删改查。可能会遇到下列的问题:

预先遍历且缓存结果,但是目录下文件可能很多,而且会动态改变;如果不缓存,多个地方可能会频繁的需要访问这一结果导致效率低下。

这时候可以使用yield定义一个生成器函数。

def get_all_dir_files(target_dir):
  for root, dirs, files in os.walk(target_dir):
    for file in files:
      file_path = os.path.join(root, file)
      yield os.path.realpath(file_path)
def file_factory(file):
  """ do something """
target_dir = './'
all_files = get_all_dir_files(target_dir)
for file in all_files:
  file_factory(file)

限于篇幅,就先介绍到这里,希望本文能让你对生成器有一个新的认识。

,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值

Python 相关文章推荐
Python使用htpasswd实现基本认证授权的例子
Jun 10 Python
python下载文件时显示下载进度的方法
Apr 02 Python
python实现数组插入新元素的方法
May 22 Python
python获取外网ip地址的方法总结
Jul 02 Python
Python中的字符串查找操作方法总结
Jun 27 Python
python开发环境PyScripter中文乱码问题解决方案
Sep 11 Python
Django框架文件上传与自定义图片上传路径、上传文件名操作分析
May 10 Python
详解Python图像处理库Pillow常用使用方法
Sep 02 Python
Django实现文件上传下载功能
Oct 06 Python
在CentOS7下安装Python3教程解析
Jul 09 Python
Python异常处理机制结构实例解析
Jul 23 Python
Python数据可视化常用4大绘图库原理详解
Oct 23 Python
利用python实现短信和电话提醒功能的例子
Aug 08 #Python
twilio python自动拨打电话,播放自定义mp3音频的方法
Aug 08 #Python
Python 使用matplotlib模块模拟掷骰子
Aug 08 #Python
Pycharm远程调试原理及具体配置详解
Aug 08 #Python
Python IDE Pycharm中的快捷键列表用法
Aug 08 #Python
python多线程与多进程及其区别详解
Aug 08 #Python
python PIL和CV对 图片的读取,显示,裁剪,保存实现方法
Aug 07 #Python
You might like
解决文件名解压后乱码的问题 将文件名进行转码的代码
2012/01/10 PHP
PHP容易忘记的知识点分享
2013/04/30 PHP
深入PHP autoload机制的详解
2013/06/09 PHP
PHP中使用CURL模拟登录并获取数据实例
2014/07/01 PHP
PHP清除字符串中所有无用标签的方法
2014/12/01 PHP
php 算法之实现相对路径的实例
2017/10/17 PHP
键盘上一张下一张兼容IE/google/firefox等浏览器
2014/01/28 Javascript
用IE重起计算机或者关机的示例代码
2014/03/10 Javascript
使用forever管理nodejs应用教程
2014/06/03 NodeJs
浅析javascript的间隔调用和延时调用
2014/11/12 Javascript
JavaScript forEach()遍历函数使用及介绍
2015/07/08 Javascript
js倒计时抢购实例
2015/12/20 Javascript
纯JS代码实现一键分享功能
2016/04/20 Javascript
JavaScript重载函数实例剖析
2016/05/13 Javascript
JS常用函数和常用技巧小结
2016/10/15 Javascript
详解Angular的数据显示优化处理
2016/12/26 Javascript
帝国cms首页列表页实现点赞功能
2017/10/30 Javascript
JS中call和apply函数用法实例分析
2018/06/20 Javascript
JavaScript实现新年倒计时效果
2018/11/17 Javascript
微信小程序实现分享商品海报功能
2019/09/30 Javascript
vue中用 async/await 来处理异步操作
2020/07/18 Javascript
vue-cli单页面预渲染seo-prerender-spa-plugin操作
2020/08/10 Javascript
jQuery实现日历效果
2020/09/11 jQuery
python素数筛选法浅析
2018/03/19 Python
Python3.5模块的定义、导入、优化操作图文详解
2019/04/27 Python
Python Django 添加首页尾页上一页下一页代码实例
2019/08/21 Python
Python socket非阻塞模块应用示例
2019/09/12 Python
BAILEY 44官网:美国制造的女性服装
2019/07/01 全球购物
Ariat英国官网:为世界顶级马术运动员制造最优质的鞋类和服装
2020/02/14 全球购物
捷克购买家具网站:JENA nábytek
2020/03/19 全球购物
支教自我鉴定
2014/01/18 职场文书
自我鉴定标准格式
2014/03/19 职场文书
祖国在我心中的演讲稿
2014/05/04 职场文书
团日活动总结模板
2014/06/25 职场文书
试用期转正后的自我评价
2014/09/21 职场文书
使用Nginx搭载rtmp直播服务器的方法
2021/10/16 Servers