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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章