python 迭代器与生成器 详解

在python中,我们经常使用for循环来遍历各种集合,例如最常用的有list,dict等等,这些集合都是可迭代对象。我们先来了解一下python中的迭代器(Iterator)。

一、迭代器

顾名思义,迭代器,自然就是用来做迭代用的(好像是废话)。以list为例,我们用list,最多的情况就是用来做循环了(循环就是迭代嘛)

>>> list = [1,2,3]
>>> dir(list)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
  • 1
  • 2
  • 3

list就有__iter__方法。如果调用此方法,则会返回一个迭代器

>>> it = list.__iter__()
>>> it
<listiterator object at 0x10fa12950>
>>> dir(it)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__length_hint__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'next']
  • 1
  • 2
  • 3
  • 4
  • 5

所谓迭代器,是指具有next方法的对象。注意调用next方式的时候,不需要任何参数。调用next方法时,迭代器会返回它的下一个值。如果迭代器没有值返回,则会抛出StopIteration的异常。

>>> it.next()
1
>>> it.next()
2
>>> it.next()
3
>>> it.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

有的同学会问,我们用list用得好好的,为什么要用什么iterator?因为list是一次性获得所有值,如果这个列表很大,需要占用很大内存空间,甚至大到内存装载不下;而迭代器则是在迭代(循环)中使用一个计算一个,对内存的占用显然小得多。

用迭代器实现Fibonacci数列

#!/usr/bin/env python
#coding:utf-8

'''
Created on 2016年5月6日

@author: lei.wang
'''

class Fibonacci(object):
    def __init__(self):
        self.a = 0
        self.b = 1

    def next(self):
        self.a,self.b = self.b,self.a + self.b
        print self.a
        return self.a

    def __iter__(self):
        return self

if __name__ == '__main__':
    fib = Fibonacci()
    for n in fib:
        if n > 10:
            #print n
            break
  • 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
  • 28
  • 29

刚才我们讲的都是从列表转为迭代器,那从迭代器能变成列表么?答案是当然可以,请看:

#!/usr/bin/env python
#coding:utf-8

'''
Created on 2016年5月6日

@author: lei.wang
'''

class MyIterator(object):
    index = 0

    def __init__(self):
        pass

    def next(self):
        self.index += 1
        if self.index > 10:
            raise StopIteration
        return self.index

    def __iter__(self):
        return self

if __name__ == '__main__':
    my_interator = MyIterator()
    my_list = list(my_interator)
    print my_list
  • 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
  • 28
  • 29
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  • 1

二、生成器

当我们调用一个普通的python函数时(其实不光是python函数,绝大部分语言的函数都是如此),一般都是从函数的第一行开始执行,直到遇到return语句或者异常或者函数的最后一行。这样,函数就将控制权交还与调用者,函数中的所有工具以及局部变量等数据都将丢失。再次调用这个函数的时候,所有的局部变量,堆栈信息都将重新创建,跟之前的调用再无关系。

有时候我们并不希望函数只返回一个值,而是希望返回一个序列,比如前面的fibonacci序列。要做到这一点,这种函数需要能够保存自己的工作状态。这样的话,就不能使用我们通常所使用的return语句,因为一旦使用return语句,代码执行的控制权就交给了函数被调用的地方,函数的所有状态将被清零。在这种情况下,我们就需要使用yield关键字。含有yield关键字的地方,就是一个生成器。

在python中,生成器通过生成器函数生成,生成器函数定义的方法跟普通函数定义的方法一致。唯一不同的地方是,生成器函数不用return返回,而是用yield关键字一次返回一个结果,在每个结果之间挂起与继续他们的状态,来自动实现迭代(循环)。

废话说了这一大堆,直接上代码,show me the code:

#!/usr/bin/env python
#coding:utf-8

'''
Created on 2016年5月6日

@author: lei.wang
'''

def myXrange(n):
    print "myXrange beginning!"
    i = 0
    while i < n:
        print "before yield, i is: ",i
        yield i
        i += 1
        print "after yield, i is: ",i
    print "myXrange endding!"        

def testMyXrange():
    my_range = myXrange(3)
    print my_range
    print "--------\n"

    print my_range.next()
    print "--------\n"

    print my_range.next()
    print "--------\n"

    print my_range.next()
    print "--------\n"    

    print my_range.next()
    print "--------\n"

testMyXrange()
  • 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
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

代码运行的结果

<generator object myXrange at 0x10b3f6b90>
--------

myXrange beginning!
before yield, i is:  0
0
--------

after yield, i is:  1
before yield, i is:  1
1
--------

after yield, i is:  2
before yield, i is:  2
2
--------

after yield, i is:  3
myXrange endding!
Traceback (most recent call last):
  File "/Users/lei.wang/code/java/pydevttt/leilei/bit/interview/myGenerator.py", line 37, in <module>
    testMyXrange()
  File "/Users/lei.wang/code/java/pydevttt/leilei/bit/interview/myGenerator.py", line 34, in testMyXrange
    print my_range.next()
StopIteration
  • 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

有代码运行的结果,我们很容易看出: 
1.当调用生成器函数时候,函数返回的,只是一个生成器对象,并没有真正执行里面的逻辑。 
2.当next()方法第一次被调用以后,生成器才真正开始工作。一旦遇到yield语句,代码便停止运行。注意此时的停止运行跟return的是不一样的。 
3.调用next()方法的时候,返回的是yield处的参数值 
4.当继续调用next()方法时,代码将在上一次停止的yield语句处继续执行,并且到下一个yield处停止。 
5.一直到后面没有yield语句,最后抛出StopIteration的异常。

生成器其实对我们来说并不陌生,请看: 
以大家都比较熟悉的列表解析式为例:

>>> list=[i for i in range(10)]
>>> list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> type(list)
<type 'list'>
  • 1
  • 2
  • 3
  • 4
  • 5

将方括号改为圆括号:

>>> gen=(i for i in range(3))
>>> gen
<generator object <genexpr> at 0x10c4a19b0>
>>> gen.next()
0
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

大伙看到没有,这就是一个典型的生成器。

再举一个我们常见的例子: 
大家都经常使用range生成一个列表做循环。注意range生成的是一个列表。那如果这个列表很大,大到内存都无法放下。那么,我们这个时候需要使用xrange了。xrange产生的就是一个生成器,就不受内存的限制。。。

用生成器产生Fibonacci序列:

#!/usr/bin/env python
#coding:utf-8

'''
Created on 2016年5月6日

@author: lei.wang
'''
class Fibonacci_generator(object):
    def __init__(self):
        self.a = 0
        self.b = 1

    def get_num(self):
        while True:
            self.a,self.b = self.b,self.a+self.b
            print self.a
            yield self.a

if __name__ == '__main__':
    fib = Fibonacci_generator()
    for n in fib.get_num():
        if n > 10:
            break
  • 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

运行上面的代码:

1
1
2
3
5
8
13
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章