欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

python iter yield

shiping1 的头像

Python的Iterator和Generator


0、下面的我说的话,不保证真实性,不负任何责任
1、所谓Iterator,就是对一个抽象的数据集合顺序访问,且隐藏了数据底层
     是如何保存和实现的。它占用更少的内容,但需要记住当前的状态,以便返回
     下一个数据项
2、Python也存在iterator interface类似的思想,但并没有实际的
     类。在Python中iterator是一个实现了next()方法的对象,实现了这个方法后,该
     对象就是iterator了
3、Python有一个内置iter()函数可以返回容器和其他对象内部的iterator对象
4、Python中所有容器都是可迭代的,for语句实际上会在容器对象中调用__iter__()方法。
     该方法会返回一个定义了next()方法的迭代器(可能就是本身),它在容器中逐一访问元素,当
    没有后续元素时,抛出一个StopIteration异常
5、for item in container类似的语句其实就是调用了内置函数iter(),例如下面的代码是一样的

      list = [1,2,,3]

      #**********
      for i in list:
      print i
      #**********

      #**********
      tempIte = iter(list)
      print tempIte.next()
      print tempIte.next()
      print tempIte.next()

       #**********

6、内置iter(object)函数需要调用object的__iter__()函数,如果一个自定义的class想像第5点描述的那样
     使用的话,这个类不仅需要定义next()方法,还要定义__iter__()方法,这个方法很多时候都只是返回这个
     类本身,即self(作为一个迭代子)
7、生成器,不太懂,抄一段
      """生成器“返回”时带关键字 yield ,但“记住”了它“返回”的所有确切执行位置。下次调用生成器时,它再接着上次的位置 — 包括函数流和变量值这两个方面。 在 Python 2.2+ 中,不直接 写生成器。相反,编写一个函数,当调用它时,返回生成器。这可能看起来有点古怪,但“函数工厂”是 Python 的常见特性,并且“生成器工厂”明显是这个概念性扩展。在 Python 2.2+ 中使函数成为生成器工厂是它主体某处的一个或多个 yield 语句。如果 yield 发生, return 一定只发生在没有伴随任何返回值的情况中。然而,一个较好的选择是,安排函数体以便于完成所有 yield 之后,执行就“跳转到结束”。但如果遇到 return ,它导致产生的生成器抛出 StopIteration 异常,而不是进一步生成值。 """

来自  http://blog.163.com/163_zhujingwei/blog/static/973305972009499037669/
 

Python关键字yield详解

迭代器(Iterator)

为了理解yield是什么,首先要明白生成器(generator)是什么,在讲生成器之前先说说迭代器(iterator),当创建一个列表(list)时,你可以逐个的读取每一项,这就叫做迭代(iteration)。

  1. mylist = [123]   
  2.  for i in mylist :   
  3.  print(i)   
  4. 1 
  5. 2 
  6. 3 

Mylist就是一个迭代器,不管是使用复杂的表达式列表,还是直接创建一个列表,都是可迭代的对象。

  1. mylist = [x*x for x in range(3)]   
  2. for i in mylist :   
  3. print(i)   
  4. 0 
  5. 1 
  6. 4 

你可以使用“for··· in ···”来操作可迭代对象,如:list,string,files,这些迭代对象非常方便我们使用,因为你可以按照你的意愿进行重复的读取。但是你不得不预先存储所有的元素在内存中,那些对象里有很多元素时,并不是每一项都对你有用。

生成器(Generators)

生成器同样是可迭代对象,但是你只能读取一次,因为它并没有把所有值存放内存中,它动态的生成值:

  1. mygenerator = (x*x for x in range(3))   
  2. for i in mygenerator :   
  3. print(i)   
  4. 0 
  5. 1 
  6. 4 

使用()和[]结果是一样的,但是,第二次执行“ for in mygenerator”不会有任何结果返回,因为它只能使用一次。首先计算0,然后计算1,之后计算4,依次类推。

Yield

Yield是关键字, 用起来像return,yield在告诉程序,要求函数返回一个生成器。

  1. def createGenerator() :   
  2. mylist = range(3)   
  3. for i in mylist :   
  4. yield i*i   
  5.     
  6. mygenerator = createGenerator() # create a generator   
  7. print(mygenerator) # mygenerator is an object!   
  8. <generator object createGenerator at 0xb7555c34>   
  9. for i in mygenerator:   
  10. print(i)   
  11. 0 
  12. 1 
  13. 4 

这个示例本身没什么意义,但是它很清晰地说明函数将返回一组仅能读一次的值,要想掌握yield,首先必须理解的是:当你调用生成器函数的时候,如上例中的createGenerator(),程序并不会执行函数体内的代码,它仅仅只是返回生成器对象,这种方式颇为微妙。函数体内的代码只有直到每次循环迭代(for)生成器的时候才会运行。

函数第一次运行时,它会从函数开始处直到碰到yield时,就返回循环的第一个值,然后,交互的运行、返回,直到没有值返回为止。如果函数在运行但是并没有遇到yield,就认为该生成器是空,原因可能是循环终止,或者没有满足任何”if/else”。

接下来读一小段代码来理解生成器的优点:

控制生成器穷举

  1. >>> class Bank(): # 创建银行,构造ATM机   
  2. ...    crisis = False 
  3. ...    def create_atm(self) :   
  4. ...        while not self.crisis :   
  5. ...            yield "$100" 
  6. >>> hsbc = Bank() # 没有危机时,你想要多少,ATM就可以吐多少   
  7. >>> corner_street_atm = hsbc.create_atm()   
  8. >>> print(corner_street_atm.next())   
  9. $100 
  10. >>> print(corner_street_atm.next())   
  11. $100 
  12. >>> print([corner_street_atm.next() for cash in range(5)])   
  13. ['$100''$100''$100''$100''$100']   
  14. >>> hsbc.crisis = True # 危机来临,银行没钱了   
  15. >>> print(corner_street_atm.next())   
  16. <type 'exceptions.StopIteration'>   
  17. >>> wall_street_atm = hsbc.ceate_atm() # 新建ATM,银行仍然没钱   
  18. >>> print(wall_street_atm.next())   
  19. <type 'exceptions.StopIteration'>   
  20. >>> hsbc.crisis = False # 麻烦就是,即使危机过后银行还是空的   
  21. >>> print(corner_street_atm.next())   
  22. <type 'exceptions.StopIteration'>   
  23. >>> brand_new_atm = hsbc.create_atm() # 构造新的ATM,恢复业务   
  24. >>> for cash in brand_new_atm :   
  25. ...    print cash   
  26. $100 
  27. $100 
  28. $100 
  29. $100 
  30. $100 
  31. $100 
  32. $100 
  33. $100 
  34. $100 

对于访问控制资源,生成器显得非常有用。

迭代工具,你最好的朋友

迭代工具模块包含了操做指定的函数用于操作迭代器。想复制一个迭代器出来?链接两个迭代器?以one liner(这里的one-liner只需一行代码能搞定的任务)用内嵌的列表组合一组值?不使用list创建Map/Zip?···,你要做的就是 import itertools,举个例子吧:

四匹马赛跑到达终点排名的所有可能性:

  1. >>> horses = [1234]   
  2. >>> races = itertools.permutations(horses)   
  3. >>> print(races)   
  4. <itertools.permutations object at 0xb754f1dc>   
  5. >>> print(list(itertools.permutations(horses)))   
  6. [(1234),   
  7.  (1243),   
  8.  (1324),   
  9.  (1342),   
  10.  (1423),   
  11.  (1432),   
  12.  (2134),   
  13.  (2143),   
  14.  (2314),   
  15.  (2341),   
  16.  (2413),   
  17.  (2431),   
  18.  (3124),   
  19.  (3142),   
  20.  (3214),   
  21.  (3241),   
  22.  (3412),   
  23.  (3421),   
  24.  (4123),   
  25.  (4132),   
  26.  (4213),   
  27.  (4231),   
  28.  (4312),   
  29.  (4321)] 

理解迭代的内部机制:

迭代(iteration)就是对可迭代对象(iterables,实现了__iter__()方法)和迭代器(iterators,实现了__next__()方法)的一个操作过程。可迭代对象是任何可返回一个迭代器的对象,迭代器是应用在迭代对象中迭代的对象,换一种方式说的话就是:iterable对象的__iter__()方法可以返回iterator对象,iterator通过调用next()方法获取其中的每一个值(译者注),读者可以结合Java API中的 Iterable接口和Iterator接口进行类比。

来自  http://developer.51cto.com/art/201301/379132.htm


 

Python 迭代器 & __iter__方法

 

迭代器就是重复地做一些事情,可以简单的理解为循环,在python中实现了__iter__方法的对象是可迭代的,实现了next()方法的对象是迭代器,这样说起来有点拗口,实际上要想让一个迭代器工作,至少要实现__iter__方法和next方法。很多时候使用迭代器完成的工作使用列表也可以完成,但是如果有很多值列表就会占用太多的内存,而且使用迭代器也让我们的程序更加通用、优雅、pythonic。

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化两个计数器a,b

    def __iter__(self):
        return self # 实例本身就是迭代对象,故返回自己

    def next(self):
        self.a, self.b = self.b, self.a + self.b # 计算下一个值
        if self.a > 100000: # 退出循环的条件
            raise StopIteration();
        return self.a # 返回下一个值

现在,试试把Fib实例作用于for循环:

>>> for n in Fib():
...     print n
...
1
1
2
3
5
...
46368
75025
迭代器是一个对象,而生成器是一个函数,迭代器和生成器是python中两个非常强大的特性,编写程序时你可以不使用生成器达到同样的效果,但是生成器让你的程序更加pythonic。创建生成器非常简单,只要在函数中加入yield语句即可。函数中每次使用yield产生一个值,函数就返回该值,然后停止执行,等待被激活,被激活后继续在原来的位置执行。下边的例子实现了同样的功能:

 

[python] view plaincopy
  1. #!/usr/bin/env python  
  2. #coding=utf-8  
  3. def fib():  
  4.     a,b = 0,1  
  5.     while 1:  
  6.         a,b = b,a+b  
  7.         yield a  
  8. for f in fib():  
  9.     if f < 10000:  
  10.         print f  
  11.     else:  
  12.         break  

如何迭代?

    根本上说, 迭代器就是有一个 next() 方法的对象, 而不是通过索引来计数. 当你或是一个循环机制(例如 for 语句)需要下一个项时, 调用迭代器的 next() 方法就可以获得它. 条目全部取出后, 会引发一个 StopIteration 异常, 这并不表示错误发生, 只是告诉外部调用者, 迭代完成.

    不过, 迭代器也有一些限制. 例如你不能向后移动, 不能回到开始, 也不能复制一个迭代器.如果你要再次(或者是同时)迭代同个对象, 你只能去创建另一个迭代器对象. 不过, 这并不糟糕,因为还有其他的工具来帮助你使用迭代器. 

    reversed() 内建函数将返回一个反序访问的迭代器. enumerate() 内建函数同样也返回迭代器.另外两个新的内建函数, any() 和 all() , 在 Python 2.5 中新增, 如果迭代器中某个/所有条目的值都为布尔真时,则它们返回值为真. 本章先前部分我们展示了如何在 for 循环中通过索引或是可迭代对象来遍历条目. 同时 Python 还提供了一整个 itertools 模块, 它包含各种有用的迭代器.

 

迭代器工作原理
   如果这是一个实际应用程序, 那么我们需要把代码放在一个 try-except 块中. 序列现在会自 
动地产生它们自己的迭代器, 所以一个 for 循环: 
[python] view plaincopy
  1. for i in seq:  
  2.     do_something_to(i)  
实际上是这样工作的:
[python] view plaincopy
  1. fetch = iter(seq)  
  2. while True:  
  3. try:  
  4. i = fetch.next()  
  5. except StopIteration:  
  6. break  
  7. do_something_to(i)  

另外, Python 还引进了三个新的内建字典方法来定义迭代: myDict.iterkeys() (通过 keys 迭 

代), myDict.itervalues() (通过 values 迭代), 以及 myDicit.iteritems() (通过 key/value 对来迭代). 注意, in 操作符也可以用于检查字典的 key 是否存在 , 之前的布尔表达式myDict.has_key(anyKey) 可以被简写为 anyKey in myDict .

===文件===

    文件对象生成的迭代器会自动调用 readline() 方法. 这样, 循环就可以访问文本文件的所有
行. 程序员可以使用 更简单的 for eachLine in myFile 替换 for eachLine in myFile.readlines() :

[python] view plaincopy
  1. >>>myFile=open(‘config-win.txt’)  
  2.   
  3. >>> for eachLine in myFile:  
  4. … print eachLine, # comma suppresses extra n  
  5. …  
  6. [EditorWindow]  
  7. font-name: courier new  
  8. font-size: 10  
  9. >>> myFile.close()  

可变对象和迭代器

    记住,在迭代可变对象的时候修改它们并不是个好主意. 这在迭代器出现之前就是一个问题.
一个流行的例子就是循环列表的时候删除满足(或不满足)特定条件的项:

[python] view plaincopy
  1. for eachURL in allURLs:  
  2.     if not eachURL.startswith(‘http://’):  
  3.         allURLs.remove(eachURL) # YIKES!!  
    除列表外的其他序列都是不可变的, 所以危险就发生在这里. 一个序列的迭代器只是记录你当前到达第多少个元素, 所以如果你在迭代时改变了元素, 更新会立即反映到你所迭代的条目上.在迭代字典的 key 时, 你绝对不能改变这个字典. 使用字典的 keys() 方法是可以的, 因为keys() 返回一个独立于字典的列表. 而迭代器是与实际对象绑定在一起的, 它将不会继续执行下去:
[python] view plaincopy
  1. >>> myDict = {‘a’: 1, ‘b’: 2, ‘c’: 3, ‘d’: 4}  
  2. >>> for eachKey in myDict:  
  3. … print eachKey, myDict[eachKey]  
  4. … del myDict[eachKey]  
  5. … a 1  
  6. Traceback (most recent call last):  
  7. File “<stdin>”, line 1in <module>  
  8. RuntimeError: dictionary changed size during iteration  

这样可以避免有缺陷的代码. 更多有关迭代器的细节请参阅 PEP 234 .

如何创建迭代器

对一个对象调用 iter() 就可以得到它的迭代器. 它的语法如下:

[python] view plaincopy
  1. iter(obj)  
  2. iter(func, sentinel)  
    如果你传递一个参数给 iter() , 它会检查你传递的是不是一个序列, 如果是, 那么很简单: 
根据索引从 0 一直迭代到序列结束. 另一个创建迭代器的方法是使用类, 我们将在第 13 章详细 
介绍, 一个实现了 __iter__() 和 next() 方法的类可以作为迭代器使用.

如果是传递两个参数给 iter() , 它会重复地调用 func , 直到迭代器的下个值等于sentinel .



来自  http://blog.csdn.net/bluebird_237/article/details/38894617

 

Python专用方法与迭代机制实例分析

 

这篇文章主要介绍了Python专用方法与迭代机制,包括类的私有方法、专有方法、模块私有对象、迭代__iter__()方法的对象等,需要的朋友可以参考下

本文实例讲述了Python专用方法与迭代机制,分享给大家供大家参考之用。具体分析如下:

众所周知,Python 设计哲学是“优雅”、“明确”、“简单”,对于一件事只用一种最好的方法来做,而这种优雅在于背后很自然的隐藏了很多细节。比如对一些对象直接用for 语句来迭代,一些全局函数可以作用于很多具有共同特征的对象,还有生成器装饰器自省等特性。其中很多实现都是借助 Python  内部专用方法,而对外则使用统一的全局函数来进行操作,在配合一些语法糖,使得 Python 写起来愈发的方便,符合人的直觉。

Python 专用方法

类的私有方法:以双线划线开头,但是不以双下划线结尾的方法;
类的专有方法:以双下划线开头和结尾,常用来被内建函数调用;
模块私有对象:以单下划线开头,不能被导入到其他的模块中去;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python
# Python3 实现
_modeluprivate = '本模块私有'  #不能用 from module import * 导入
 
class People():
  def __myprivate(self):
    print("This is a private fun")
  def __test__(self):
    print('call __private: ',end='')
    self.__myprivate()
 
if __name__ == '__main__':
  a = People()
  a.__test__()      # 专有方法,一般系统专用,自己的类方法不要用这种新式命名
  a._People__myprivate() # 私有方法被对外被翻译成了这种名字,从而达到私有的效果
  print(_modeluprivate)
   
'''''
输出
call __private: This is a private fun
This is a private fun
本模块私有
'''

Python 迭代机制

Python 中的可迭代对象是实现了 __iter__() 方法的对象,而 __iter__() 方法返回一个迭代器对象,迭代器对象内部要实现 __next__() 方法。迭代器对外提供了一个统一的遍历集合的接口,并且可以直接用 for 语句来进行操作,非常的方便。对于一些特别大甚至无限的集合,迭代器避免了一次性将数据集载入,几乎是唯一的访问方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python
# Python3 实现
class IterTest():
  def __init__(self):
    self.a = 0
  def __iter__(self):
    return self
  def __next__(self):
    self.a += 1
    if self.a > 3:
      raise StopIteration
    return self.a
 
if __name__ == '__main__':
  a = IterTest()
  for i in a:
    print(i,end=' ')
  b = IterTest()
  print(list(b)) # list()构造器,可以接受可迭代对象
  c = IterTest()
  print(next(c), next(c), next(c))
 
'''''
输出
1 2 3 [1, 2, 3]
1 2 3
'''

Python 的生成器其实返回的也是一个迭代器,同样可以对其使用 next() 函数,对其使用 for操作,有了 yield 关键字使得创建生成器更加的方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
# Python3 实现
def funGenerate():
  yield 1
  yield 2
  yield 3
 
if __name__ == '__main__':
  a = funGenerate()
  for i in a:
    print(i, end=' ')
  b = funGenerate()
  print(next(b),next(b),next(b))
 
'''''
输出
1 2 3 1 2 3
'''

希望本文所述对大家Python程序设计的学习有所帮助。

来自  http://www.jb51.net/article/55196.htm

 

从python的yield说起

 

前段时间在读trac 中wiki模块的源码的时候,发现了很多地方都使用了yiled这一关键词,

感觉是在需要返回某个值的地方通过yield来代替return,

不是很明白其用法,所以仔细研究下。

 

一个使用了yiled关键字的函数就不再是一个普通的函数了,而是一个生成器函数(generator function)

当函数被调用的时候将返回一个迭代器(iterator)。

 

所以下面将分别讲解迭代器和生成器这两个概念。

 

一. 迭代器(Iterator)

 

 写道
迭代器是一个对象,它实现了迭代器协议,
一般需要实现如下两个方法
1)next方法
返回容器的下一个元素
2)__iter__方法
返回迭代器自身
 

 

对于for语言大家可能都不陌生,我们很多时候需要对一些容器对象进行遍历就会使用到for循环:

 

Python代码  收藏代码
  1. l=[0,1,2,3,4,5,6]  
  2. for i in l:  
  3.     print i  
 

l是一个type为list的对象,这段for-in代码在运行的时候其实是调用了l的__iter__()函数,返回了一个实现了__next__()或next()(各个版本的python可能不一样,我试验的时候所使用的版本为2.6.2)的迭代器对象,每循环一次就会通过next取下一个元素。

 

当然我们完全没有必要先把所有的元素都算出来放到一个list里或者其他容器类里进行循环,这样比较浪费空间。

我们可以直接创建自己的一个迭代器。

 

Python代码  收藏代码
  1. # -*- coding: utf-8 -*-  
  2.   
  3. '''''Fibonacci iterator'''  
  4.   
  5. class Fib:  
  6.     '''''一个可以生成Fibonacci 数列的迭代器'''  
  7.   
  8.     def __init__(self, max):  
  9.         self.max = max  
  10.   
  11.     def __iter__(self):  
  12.         self.a = 0  
  13.         self.b = 1  
  14.         return self  
  15.   
  16.     def next(self):  
  17.         fib = self.a  
  18.         if fib > self.max:  
  19.             raise StopIteration  
  20.         self.a, self.b = self.b, self.a + self.b  
  21.         return fib  
 

 

定义好了这个Fibonacci迭代器,我们就可以来使用它了。

 

 

Python代码  收藏代码
  1. from fibonacci2 import Fib  
  2. for n in Fib(1000):  
  3.     print n  
 

当调用Fib(1000)的时候,将生成一个迭代器对象,每一次循环都将调用一次next取到下一个值。

所以我们可以看出迭代器有一个很核心的东西就是在循环中,迭代器可以记住之前的状态。

 

 

二.生成器

 

前面我们说了,任何使用了yield关键字的函数都不再是普通的函数了,我们还是来看实例吧,这样比较容易理解

 

 

Python代码  收藏代码
  1. def fib(max):  
  2.     a, b = 01            
  3.     while a < max:  
  4.         yield a            
  5.         a, b = b, a + b    

   这里简单的几行代码就实现了上面的迭代器类那么一大堆代码所实现的功能

 

   使用的时候和上面很类似:

 

 

Python代码  收藏代码
  1. from fibonacci import fib  
  2. for n in fib(1000):  
  3.     print n  

 

引文fib函数使用了yield所以它是一个生成器函数,当我们调用fib(1000)的时候它其实是返回了一个迭代器,且这个迭代器可以控制生成器函数的运行。

我们通过这个返回的迭代器的动作控制fib这个生成器函数的运行。

每当调用一次迭代器的next函数,生成器函数运行到yield之处,返回yield后面的值且在这个地方暂停,所有的状态都会被保持住,直到下次next函数被调用,或者碰到异常循环退出。

 

所以生成器的概念还是很简单的。

 

三.总结

 

1.for-in语句在底层都是对一个迭代器对象进行操作的

2.使用了yield关键字的函数就是一个生成器函数,被调用的时候生成一个可以控制自己运行的迭代器。

 

分享到:  
评论
6 楼 gmizr 2010-02-26  
使用yield未必会比自己写__init__,next速度慢,我也只是想当然,还没有实验过 

python官方的tutorial也包含了yield的内容 
9.10. Generators
5 楼 lzyzizi 2010-02-22  
对PYTHON的理解还不是很深,感觉yield和RUBY的yield有点像。 
敢问楼上各位我是否可以把这里的yield看做是一个template或者visitor里的一个留个未来实现的“抽象函数”?
4 楼 lukejin 2010-02-21  
calmness 写道
yield是记住了多个断点的状态,那在性能上应该是比传统的迭代器速度慢

但是比传统的方便很多的。
3 楼 calmness 2010-02-21  
yield是记住了多个断点的状态,那在性能上应该是比传统的迭代器速度慢
2 楼 mikeandmore 2010-02-11  
lukejin 写道
其实呢 yield可以从协程(Coroutines)这个角度来看待,生成器就好像一个生产者,而所关联的那个迭代器就是一个消费者。

而且还是双向的-w- 
 
1 楼 lukejin 2010-02-03  
其实呢 yield可以从协程(Coroutines)这个角度来看待,生成器就好像一个生产者,而所关联的那个迭代器就是一个消费者。


来自  http://lukejin.iteye.com/blog/587051

 

python 中yield的用法详解


包含了关键字"yield"的函数就不是普通的函数。当含有这个关键字的函数被调用的时候,这个函数在遇到yield的时候会停止运行,并且返回一个迭代器(iterator)。每次请求一个值,就会执行生成的代码。直到遇到一个yield或者return。 首先,我们先了解什么是迭代器。

lst=[1,2,3,4,5]
for i in lst
    print i

从这个例子我们可以看的出来,每循环一次i的值就会指向列表的下一个元素,大家认为这是正常的,那么为什么i会得到列表的下一个元素呢? 其实在for的循环中列表就使用了迭代器。每一次循环迭代器就使用next方法返回一个值。当然这个迭代是隐形的,大家是看不见的。 我们可以实现一个可迭代的函数。

#!/ust/bin/env python
class IterExample():
    def __init__(self):
        self.a = 0
    def next(self):
        self.a += 1
        if self.a > 10:raise StopIteration
        return self.a
    def __iter__(self):
        return self
ie = IterExample()
for i in ie:
    print i

上面的列表默认已经具备了迭代方法,不用我们实现。如果一个函数不是可迭代的那是不能用在循环里的。 下面我们来解决yield 其实这个很简单。只不过大家看的例子复杂了。看看下面的例子你立刻就会理解它。

#!/usr/bin/env python
#__metaclass__ = type
def gen():
        print 'enter'
        yield 1
        print 'next'
        yield 2
        print 'next again'
for i in gen():
        print i
#########################
                                这个例子打印如下:

enter 1 next 2 next again; 我来解释一下这个程序: 首先大家要知道为什么这个函数可以用在for循环中。不用问,因为这个函数是可迭代的,也就是这个函数可以每次都返回一个值。 但是我们在gen()函数里看不到__iter__()和next() 方法。其实它隐藏在yield里。高级语言就是这样,隐藏了好多东西。这个和c 语言就不同了。C语言可以看到每个细节。在这里程序执行到yield 1;的时候就停止了,下面的程序不再执行,然后返回一个值“1”. 当下一个for的之后程序接着往下执行到yeild 2;。程序停止执行,然会返回一个值“2”。但是还有一个问题就是,最后yield下面的"next again;" 会打印出来呢?这也可能就是在执行完最后一个yield 的时候,for i; in; gen;()的时候, 发生了一些什么动作,导致最后一个yield后面的代码也被执行了。 如果我们修改一下这个程序如下:

#!/usr/bin/env python
#__metaclass__ = type
def gen():
        print 'enter'
        yield 1
        print 'next'
        return
        print 'next 2'
        yield 2
        print 'next 3'
for i in gen():
        print i

#######################

程序打印如下: enter 1 next 看明白了这就是yield和return的区别。yield可以向下运行。而return返回后这个函数余下的部分就不能执行了。

 

Python yield 使用浅析

初学 Python 的开发者经常会发现很多 Python 函数中用到了 yield 关键字,然而,带有 yield 的函数执行流程却和普通函数不一样,yield 到底用来做什么,为什么要设计 yield ?本文将由浅入深地讲解 yield 的概念和用法,帮助读者体会 Python 里 yield 简单而强大的功能。

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

我们先抛开 generator,以一个常见的编程题目来展示 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),我们可以得到如下输出:

 >>> fab(5) 
 1 
 1 
 2 
 3 
 5

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

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

清单 2. 输出斐波那契數列前 N 个数第二版
 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

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

 >>> for n in fab(5): 
 ...     print n 
 ... 
 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. 第三个版本
 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()

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

 >>> for n in Fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5

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

清单 5. 使用 yield 的第四版
 def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield b 
        # print b 
        a, b = b, a + b 
        n = n + 1 

'''

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

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

 >>> for n in fab(5): 
 ...     print n 
 ... 
 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 中调试通过

来自 http://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/

 

Comment utiliser yield et les générateurs en Python ? 12


Les générateurs sont une fonctionalité fabuleuse de Python, et une étape indispensable dans la maîtrise du langage. Une fois compris,vous ne pourrez plus vous en passer.

Rappel sur les itérables

Quand vous lisez des éléments un par un d’une liste, on appelle cela l’itération:

lst = [1, 2, 3]
>>> for i in lst :
...     print(i)
1
2
3

Et quand on utilise une liste en intention, on créé une liste, donc un itérable. Encore une fois, avec une boucle for, on prend ses éléments un par un, donc on itère dessus:

lst = [x*x for x in range(3)]
>>> for i in lst :
...     print(i)
0
1
4

À chaque fois qu’on peut utiliser “for in…” sur quelque chose, c’est un itérable : lists, strings, files…

Ces itérables sont pratiques car on peut les lire autant qu’on veut, mais ce n’est pas toujours idéal car on doit stocker tous les éléments en mémoire.

Les générateurs

Si vous vous souvenez de l’article sur les comprehension lists, on peut également créer des expressions génératrices:

generateur = (x*x for x in range(3))
>>> for i in generateur :
...     print(i)
0
1
4

La seule différence avec précédemment, c’est qu’on utilise () au lieu de []. Mais on ne peut pas lire generateur une seconde fois car le principe des générateurs, c’est justement qu’ils génèrent tout à la volée: ici il calcule 0, puis l’oublie, puis calcule 1, et l’oublie, et calcule 4. Tout ça un par un.

Le mot clé yield

yield est un mot clé utilisé en lieu et place de return, à la différence prêt qu’on va récupérer un générateur.

>>> def creerGenerateur() :
...     mylist = range(3)
...     for i in mylist:
...         yield i*i
...
>>> generateur = creerGenerateur() # crée un générateur
>>> print(generateur) # generateur est un objet !
< generator object creerGenerateur at 0x2b484b9addc0>
>>> for i in generateur:
...     print(i)
0
1
4

Ici c’est un exemple inutile, mais dans la vraie vie vivante, c’est pratique quand on sait que la fonction va retourner de nombreuses valeurs qu’on ne souhaite lire qu’une seule fois.

Le secret des maîtres Zen qui ont acquis la compréhension transcendantale de yield, c’est de savoir que quand on appelle la fonction, le code de la fonction n’est pas exécute. A la place, la fonction va retourner un objet générateur.

C’est pas évident à comprendre, alors relisez plusieurs fois cette partie.

creerGenerateur() n’exécute pas le code de creerGenerateur.

creerGenerateur() retourne un objet générateur.

En fait, tant qu’on ne touche pas au générateur, il ne se passe rien. Puis, dès qu’on commence à itérer sur le générateur, le code de la fonction s’exécute.

La première fois que le code s’éxécute, il va partir du début de la fonction, arriver jusqu’à yield, et retourner la première valeur. Ensuite, à chaque nouveau tour de boucle, le code va reprendre de la où il s’est arrêté (oui, Python sauvegarde l’état du code du générateur entre chaque appel), et exécuter le code à nouveau jusqu’à ce qu’il rencontre yield. Donc dans notre cas, il va faire un tour de boucle.

Il va continuer comme ça jusqu’à ce que le code ne rencontre plus yield, et donc qu’il n’y a plus de valeur à retourner. Le générateur est alors considéré comme définitivement vide. Il ne peut pas être “rembobiné”, il faut en créer un autre.

La raison pour laquelle le code ne rencontre plus yield est celle de votre choix: condition if/else, boucle, recursion… Vous pouvez même yielder à l’infini.

Un exemple concret et un café, plz

yield permet non seulement d’économiser de la mémoire, mais surtout de masquer la complexité d’un algo derrière une API classique d’itération.

Supposez que vous ayez une fonction qui – tada ! – extrait les mots de plus de 3 caractères de tous les fichiers d’un dossier.

Elle pourrait ressembler à ça:

import os
 
def extraire_mots(dossier):
    for fichier in os.listdir(dossier):
        with open(os.path.join(dossier, fichier)) as f:
            for ligne in f:
                for mot in ligne.split():
                    if len(mot) > 3:
                        yield mot

Vous avez là un algo dont on masque complètement la complexité, car du point de vue de l’utilisateur, il fait juste ça:

for mot in extraire_mots(dossier):
    print mot

Et pour lui c’est transparent. En plus, il peut utiliser tous les outils qu’on utilise sur les itérables d’habitude. Toutes les fonctions qui acceptent les itérables acceptent donc le résultat de la fonction en paramètre grâce à la magie du duck typing. On créé ainsi une merveilleuse toolbox.

Controller yield

>>> class DistributeurDeCapote():
    stock = True
    def allumer(self):
        while self.stock:
            yield "capote"
...

Tant qu’il y a du stock, on peut récupérer autant de capotes que l’on veut.

>>> distributeur_en_bas_de_la_rue = DistributeurDeCapote()
>>> distribuer = distributeur_en_bas_de_la_rue.allumer()
>>> print distribuer.next()
capote
>>> print distribuer.next()
capote
>>> print([distribuer.next() for c in range(4)])
['capote', 'capote', 'capote', 'capote']

Dès qu’il n’y a plus de stock…

>>> distributeur_en_bas_de_la_rue.stock = False
>>> distribuer.next()
Traceback (most recent call last):
  File "<ipython-input-22-389e61418395>", line 1, in <module>
    distribuer.next()
StopIteration
< type 'exceptions.StopIteration'>

Et c’est vrai pour tout nouveau générateur:

>>> distribuer = distributeur_en_bas_de_la_rue.allumer()
>>> distribuer.next()
Traceback (most recent call last):
  File "<ipython-input-24-389e61418395>", line 1, in <module>
    distribuer.next()
StopIteration

Allumer une machine vide n’a jamais permis de remplir le stock ;-) Mais il suffit de remplir le stock pour repartir comme en 40:

>>> distributeur_en_bas_de_la_rue.stock = True
>>> distribuer = distributeur_en_bas_de_la_rue.allumer()
>>> for c in distribuer :
...     print c
capote
capote
capote
capote
capote
capote
capote
capote
capote
capote
capote
capote
...

itertools: votre nouveau module favori

Le truc avec les générateurs, c’est qu’il faut les manipuler en prenant en compte leur nature: on ne peut les lire qu’une fois, et on ne peut pas déterminer leur longeur à l’avance. itertools est un module spécialisé la dedans: map, zip, slice… Il contient des fonctions qui marchent sur tous les itérables, y compris les générateurs.

Et rappelez-vous, les strings, les listes, les sets et même les fichiers sont itérables.

Chaîner deux itérables, et prendre les 10 premiers caractères ? Piece of cake !

>>> import itertools
>>> d = DistributeurDeCapote().allumer()
>>> generateur = itertools.chain("12345", d)
>>> generateur = itertools.islice(generateur, 0, 10)
>>> for x in generateur:
...     print x
...     
1
2
3
4
5
capote
capote
capote
capote
capote

Les dessous de l’itération

Sous le capôt, tous les itérables utilisent un générateur appelé “itérateur”. On peut récupérer l’itérateur en utiliser la fonction iter() sur un itérable:

>>> iter([1, 2, 3])
< listiterator object at 0x7f58b9735dd0>
>>> iter((1, 2, 3))
< tupleiterator object at 0x7f58b9735e10>
>>> iter(x*x for x in (1, 2, 3))
< generator object  at 0x7f58b9723820>

Les itérateurs ont une méthode next() qui retourne une valeur pour chaque appel de la méthode. Quand il n’y a plus de valeur, ils lèvent l’exception StopIteration:

>>> gen = iter([1, 2, 3])
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
3
>>> gen.next()
Traceback (most recent call last):
  File "< stdin>", line 1, in < module>
StopIteration

Message à tous ceux qui pensent que je fabule quand je dis qu’en Python on utilise les exceptions pour contrôler le flux d’un programme (sacrilège !): ceci est le mécanisme des boucles interne en Python. Les boucles for utilisent iter() pour créer un générateur, puis attrappent une exception pour s’arrêter. À chaque boucle for, vous levez une exception sans le savoir.

Pour la petite histoire, l’implémentation actuelle est que iter() appelle la méthode __iter__() sur l’objet passé en paramètre. Donc ça veut dire que vous pouvez créer vos propres itérables:

>>> class MonIterableRienQuaMoi(object):
...     def __iter__(self):
...         yield 'Python'
...         yield "ça"
...         yield 'déchire'
...
>>> gen = iter(MonIterableRienQuaMoi())
>>> gen.next()
'Python'
>>> gen.next()
'ça'
>>> gen.next()
'déchire'
>>> gen.next()
Traceback (most recent call last):
  File "< stdin>", line 1, in < module>
StopIteration
>>> for x in MonIterableRienQuaMoi():
...     print x
...
Python
ça
déchire

12 thoughts on “Comment utiliser yield et les générateurs en Python ?

  • Poulet 2.0

    Petit typo dans l’exemple concret (merci pour le café):

            for mot in ligne.s<strong>p</strong>lit():
  • Sam Post author

    :-)

    Merci à tous les deux.

    (la flemme de mettre un tampon…)

  • Titus Crow

    Petite coquille également :
    for mot in extraire_mots(dossier):

    (pour les gens qui reprennent vos exemples en copié-collé ^^’)

  • Feadurn

    Un tout tout grand merci pour ce blog, apprenant python sur le tas pour les necessite de ma recherche, je peux enfin arriver a faire des choses un peu plus complexe grace a vous. La par exemple je suis dans le tuto sur les classes, et je vais peut etre enfin arriver a comprendre la POO.

    Pour que ce message soit un tantinet utile, dans la phrase

    “creerGenerateur() n’éxécute pas le code de creerGenerateur.creerGenerateur() retourne un objet générateur.” il manque peut etre un “mais” (ok c’etait pas si utile que ca finalement)

  • Sam Post author

    Effectivement y a moyen de rendre ça plus clair. J’ai fais un édit :-)

  • Krikor

    Si j’ai bien compris, l’intérêt de yield est de ne pas stocker en mémoire une grosse liste d’élément pour s’en servir mais d’aller chercher l’info dont on a besoin, s’en servir et tout de suite l’éliminer de la mémoire ?
    J’espère que je ne dis pas de bêtises, c’est juste pour bien comprendre quand utiliser yield plutôt que de retourner une liste.

  • Sam Post author

    Tout à fait. Yield permet aussi d’applanir des algorithmes complexes pour les exposer comme le parcours d’une liste.

  • policier moustachu

    Yo ! Je me suis fais un petit algo récursif pour calculer toutes les combinaisons de n entiers dont la somme fait m.
    Et pour pas exploser la pile je me demandais si c’était possible d’utiliser yield. Mais c’est pas évident évident …


    def pilepoile(n, taille):
    if taille == 1:
    return [[n]]
    else:
    toutes_les_listes = []
    for i in range(n + 1):
    intermediates = pilepoile(n-i, taille -1)
    for l in intermediates:
    l.insert(0,i)
    toutes_les_listes.extend(intermediates)
    return toutes_les_listes

  • policier moustachu

    def pilepoile(n, taille):
    if taille == 1:
    return [[n]]
    else:
    toutes_les_listes = []
    for i in range(n + 1):
    intermediates = pilepoile(n-i, taille -1)
    for l in intermediates:
    l.insert(0,i)
    toutes_les_listes.extend(intermediates)
    return toutes_les_listes

  • policier moustachu

    Désolé j’arrive pas à utiliser les tags de code proprement tamponnez moi fort.

  • GUILLAUME LE GALL

    Très bonne explication qui permet d’apréhender les subtilités des générateurs !

Leave a comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 

 

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.

来自  http://sametmax.com/comment-utiliser-yield-et-les-generateurs-en-python/





 

普通分类: