在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