1. 程式人生 > >python 迭代器與生成器 詳解

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']

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']

所謂迭代器,是指具有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

有的同學會問,我們用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

剛才我們講的都是從列表轉為迭代器,那從迭代器能變成列表麼?答案是當然可以,請看:

#!/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]

二、生成器

當我們呼叫一個普通的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()

程式碼執行的結果

<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.當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'>

將方括號改為圓括號:

>>> 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

大夥看到沒有,這就是一個典型的生成器。

再舉一個我們常見的例子:
大家都經常使用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
1
2
3
5
8
13