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)

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

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

(0)

相关推荐

  • 浅谈Python生成器generator之next和send的运行流程(详解)

    对于普通的生成器,第一个next调用,相当于启动生成器,会从生成器函数的第一行代码开始执行,直到第一次执行完yield语句(第4行)后,跳出生成器函数. 然后第二个next调用,进入生成器函数后,从yield语句的下一句语句(第5行)开始执行,然后重新运行到yield语句,执行后,跳出生成器函数,后面再次调用next,依次类推. 下面是一个列子: def consumer(): r = 'here' for i in xrange(3): yield r r = '200 OK'+ str(i)

  • 彻底理解Python中的yield关键字

    阅读别人的python源码时碰到了这个yield这个关键字,各种搜索终于搞懂了,在此做一下总结: 通常的for...in...循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件.它可以是mylist = [1, 2, 3],也可以是mylist = [x*x for x in range(3)].它的缺陷是所有数据都在内存中,如果有海量数据的话将会非常耗内存. 生成器是可以迭代的,但只可以读取它一次.因为用的时候才生成.比如 mygenerator = (x*x

  • Python生成器(Generator)详解

    通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了. 所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间.在Python中,这种一边循环一边计算的机制,称为生成器(Generator). 简单生成器 要创建一个generator,有很

  • Python生成器generator用法示例

    本文实例分析了Python生成器generator用法.分享给大家供大家参考,具体如下: 生成器generator本质是一个函数,它记住上一次在函数体中的位置,在生成器函数下一次调用,会自动找到该位置,局部变量都保持不变 l = [x * 2 for x in range(10)] # 列表生成式 g = (x * 2 for x in range(10)) print(l,g) # l打印的是一个列表,g则是一个generator的内存地址 一次性打印获取generator的所有元素: for

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

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

  • Python 3中的yield from语法详解

    前言 最近在捣鼓Autobahn,它有给出个例子是基于asyncio 的,想着说放到pypy3上跑跑看竟然就--失败了. pip install asyncio直接报invalid syntax,粗看还以为2to3处理的时 候有问题--这不能怪我,好-多package都是用2写了然后转成3的--结果发 现asyncio本来就只支持3.3+的版本,才又回头看代码,赫然发现一句 yield from:yield我知道,但是yield from是神马? PEP-380 好吧这个标题是我google出来

  • Python使用迭代器捕获Generator返回值的方法

    本文实例讲述了Python使用迭代器捕获Generator返回值的方法.分享给大家供大家参考,具体如下: 用for循环调用generator时,发现拿不到generator的return语句的返回值.如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中: #!/usr/bin/env python # -*- coding: utf-8 -*- def fib(max): n, a, b = 0, 0, 1 while n < max:

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

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

  • python列表生成器常用迭代器示例详解

    目录 列表生成式基础语法 1. 使用列表生成式,一行解决for循环 2. 双层循环 3. 加判断语句,条件过滤 4. 加入函数 5. 常见几种迭代器:range. zip . enumerate . filter . reduce 列表生成式基础语法 [exp for iter_var in iterable (if conditional)] 原理: 首先迭代 iterable 里所有内容,每一次迭代,都把iterable里相应的内容放在iter_var中,再把表达式exp应用该iter_va

  • Python函数生成器原理及使用详解

    1.python函数运行原理 import inspect frame = None def foo(): bar() def bar(): global frame frame = inspect.currentframe() pass # python解释器 python.exe 会用一个叫做PyEval_EvalFrameEx(c语言函数)去执行foo函数,首先会创建一个栈帧(stack frame), """ python在运行前会编译成字节码对象 当foo调用bar

  • python中的迭代器,生成器与装饰器详解

    目录 迭代器 生成器 装饰器 总结 迭代器 每一个可迭代类内部都要实现__iter__()方法,返回一个迭代类对象,迭代类对象则定义了这个可迭代类如何迭代. for循环调用list本质上是是调用了list的迭代器进行迭代. # 对list进行for循环本质上是调用了list的迭代器 list = [1,2,3,4] # for 循环调用 for elem in list: print(elem) # 迭代器调用 list_iter = list.__iter__() while True: tr

  • 一个Python优雅的数据分块方法详解

    目录 1.背景 2.islice 2.1示例 2.2只指定步长 3.iter 3.1常规使用 3.2进阶使用 4.islice 和 iter 组合使用 5.总结 1.背景 看到这个标题你可能想一个分块能有什么难度?还值得细说吗,最近确实遇到一个有意思的分块函数,写法比较巧妙优雅,所以写一个分享. 日前在做需求过程中有一个对大量数据分块处理的场景,具体来说就是几十万量级的数据,分批处理,每次处理100个.这时就需要一个分块功能的代码,刚好项目的工具库中就有一个分块的函数.拿过函数来用,发现还挺好用

  • JavaScript Generator异步过度的实现详解

    目录 异步过渡方案Generator 1. Generator 的使用 2. Generator 函数的执行 2.1 yield 关键字 2.2 next 方法与 Iterator 接口 3. Generator 中的错误处理 4. 用 Generator 组织异步方法 5. Generator 的自动执行 5.1 自动执行器的实现 5.2 基于Promise的执行器 5.3 使用 co 模块来自动执行 异步过渡方案Generator 在使用 Generator 前,首先知道 Generator

  • Python 异步之推导式示例详解

    目录 引言 1. 什么是异步推导式 2. 推导式 3. 异步推导式 4. Await 推导式 引言 当我们想到“pythonic”时,理解,如列表和字典理解是 Python 的一个特性. 这是我们执行循环的一种方式,与许多其他语言不同. Asyncio 允许我们使用异步推导式. 我们可以通过“async for”表达式使用异步推导式来遍历异步生成器和异步迭代器. 1. 什么是异步推导式 异步推导式是经典推导式的异步版本.Asyncio 支持两种类型的异步推导式,它们是“async for”推导式

  • Python中协程用法代码详解

    本文研究的主要是python中协程的相关问题,具体介绍如下. Num01–>协程的定义 协程,又称微线程,纤程.英文名Coroutine. 首先我们得知道协程是啥?协程其实可以认为是比线程更小的执行单元. 为啥说他是一个执行单元,因为他自带CPU上下文.这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程. 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的. Num02–>协程和线程的差异 那么这个过程看起来和线程差不多.其实不然, 线程切换从系统层面远不止保存和恢复 CP

  • 对python中的高效迭代器函数详解

    python中内置的库中有个itertools,可以满足我们在编程中绝大多数需要迭代的场合,当然也可以自己造轮子,但是有现成的好用的轮子不妨也学习一下,看哪个用的顺手~ 首先还是要先import一下: #import itertools from itertools import * #最好使用时用上面那个,不过下面的是为了演示比较 常用的,所以就直接全部导入了 一.无限迭代器: 由于这些都是无限迭代器,因此使用的时候都要设置终止条件,不然会一直运行下去,也就不是我们想要的结果了. 1.coun

  • Python异步之迭代器如何使用详解

    目录 正文 1. 什么是异步迭代器 1.1. Iterators 1.2. Asynchronous Iterators 2. 什么是“async for”循环? 3. 如何使用异步迭代器 4. 异步迭代器示例 正文 迭代是 Python 中的基本操作.我们可以迭代列表.字符串和所有其他结构. Asyncio 允许我们开发异步迭代器.我们可以通过定义一个实现 aiter() 和 anext() 方法的对象来在 asyncio 程序中创建和使用异步迭代器. 1. 什么是异步迭代器 异步迭代器是一个

随机推荐