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

 更新时间:2019年08月08日 09:01:16   作者:alpha_panda  
这篇文章主要介绍了Python generator生成器和yield表达式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

前言

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)

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

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

相关文章

  • Pytorch 解决自定义子Module .cuda() tensor失败的问题

    Pytorch 解决自定义子Module .cuda() tensor失败的问题

    这篇文章主要介绍了Pytorch 解决自定义子Module .cuda() tensor失败的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-06-06
  • python f-string式格式化听语音流程讲解

    python f-string式格式化听语音流程讲解

    在本篇文章中小编给大家整理的是关于python f-string式格式化听语音的相关知识点内容,有兴趣的朋友们学习下。
    2019-06-06
  • Python表格数据处理库之tablib库详解

    Python表格数据处理库之tablib库详解

    这篇文章主要介绍了Python表格数据处理库之tablib库详解,Tablib是一个用于处理电子表格数据的Python库,它可以轻松地进行数据的导入和导出,以及数据格式的转换,需要的朋友可以参考下
    2023-08-08
  • Python和Perl绘制中国北京跑步地图的方法

    Python和Perl绘制中国北京跑步地图的方法

    当你在一个城市,穿越大街小巷,跑步跑了几千公里之后,一个显而易见的想法是,我到底和之前比快了多少,跑量有何变化,如果能把在这个城市的所有路线全部画出来,会是怎样的景象呢,接下来通过本文给大家介绍Python和Perl绘制中国北京跑步地图的方法,需要的朋友参考下
    2016-03-03
  • 通用的Django注册功能模块实现方法

    通用的Django注册功能模块实现方法

    这篇文章主要介绍了通用的Django注册功能模块实现步骤,帮助大家更好的理解和使用django,感兴趣的朋友可以了解下
    2021-02-02
  • Tornado Web服务器多进程启动的2个方法

    Tornado Web服务器多进程启动的2个方法

    这篇文章主要介绍了Tornado Web服务器多进程启动的2个方法,Tornado是一个用Ptyhon写的WEB服务器,需要的朋友可以参考下
    2014-08-08
  • python基于pdfminer库提取pdf文字代码实例

    python基于pdfminer库提取pdf文字代码实例

    这篇文章主要介绍了python 提取pdf文字代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • Pytorch 的损失函数Loss function使用详解

    Pytorch 的损失函数Loss function使用详解

    今天小编就为大家分享一篇Pytorch 的损失函数Loss function使用详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-01-01
  • pyspark自定义UDAF函数调用报错问题解决

    pyspark自定义UDAF函数调用报错问题解决

    这篇文章主要为大家介绍了pyspark自定义UDAF函数调用报错问题解决,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Python中的两个列表数值加法

    Python中的两个列表数值加法

    这篇文章主要介绍了Python中的两个列表数值加法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08

最新评论