Python迭代器,生成器--精華中的精華

1. 迭代器

迭代器是訪問集合元素的一種方式。迭代器對象從集合的第一個元素開始訪問,直到全部的元素被訪問完結束。迭代器只能往前不會後退,不過這也沒什麼,由於人們不多在迭代途中日後退。另外,迭代器的一大優勢是不要求事先準備好整個迭代過程當中全部的元素。迭代器僅僅在迭代到某個元素時才計算該元素,而在這以前或以後,元素能夠不存在或者被銷燬。這個特色使得它特別適合用於遍歷一些巨大的或是無限的集合,好比幾個G的文件。python

特色:linux

a)訪問者不須要關心迭代器內部的結構,僅需經過next()方法或不斷去取下一個內容express

b)不能隨機訪問集合中的某個值 ,只能從頭至尾依次訪問編程

c)訪問到一半時不能往回退vim

d)便於循環比較大的數據集合,節省內存併發

e)也不能複製一個迭代器。若是要再次(或者同時)迭代同一個對象,只能去建立另外一個迭代器對象。enumerate()的返回值就是一個迭代器,咱們以enumerate爲例:app

 a = enumerate(['a','b'])

for i in range(2):    #迭代兩次enumerate對象
     for x, y in a:
         print(x,y)
     print(''.center(50,'-')) 

結果:異步

0 a
1 b
--------------------------------------------------
--------------------------------------------------

能夠看到再次迭代enumerate對象時,沒有返回值;ide

咱們能夠用linux的文件處理命令vim和cat來理解一下:函數

a) 讀取很大的文件時,vim須要好久,cat是毫秒級;由於vim是一次性把文件所有加載到內存中讀取;而cat是加載一行顯示一行

b) vim讀寫文件時能夠前進,後退,能夠跳轉到任意一行;而cat只能向下翻頁,不能倒退,不能直接跳轉到文件的某一頁(由於讀取的時候這個「某一頁「可能尚未加載到內存中)

 

正式進入python迭代器以前,咱們先要區分兩個容易混淆的概念:可迭代對象和迭代器;

能夠直接做用於for循環的對象統稱爲可迭代對象(Iterable)。

能夠被next()函數調用並不斷返回下一個值的對象稱爲迭代器(Iterator)。

全部的Iterable都可以經過內置函數iter()來轉變爲Iterator。

1)可迭代對象

首先,迭代器是一個對象,不是一個函數;是一個什麼樣的對象呢?就是隻要它定義了能夠返回一個迭代器的__iter__方法,或者定義了能夠支持下標索引的__getitem__方法,那麼它就是一個可迭代對象。

python中大部分對象都是可迭代的,好比list,tuple等。若是給一個準確的定義的話,看一下list,tuple類的源碼,都有__iter__(self)方法。

常見的可迭代對象:

a) 集合數據類型,如listtupledictsetstr等;

b) generator,包括生成器和帶yield的generator function。

注意:生成器都是Iterator對象,但listdictstr雖然是Iterable,卻不是Iterator,關於生成器,繼續往下看

如何判斷一個對象是可迭代對象呢?能夠經過collections模塊的Iterable類型判斷:

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

2)迭代器

一個可迭代對象是不能獨立進行迭代的,Python中,迭代是經過for ... in來完成的。

for循環在迭代一個可迭代對象的過程當中都作了什麼呢?

a)當for循環迭代一個可迭代對象時,首先會調用可迭代對象的__iter__()方法,然咱們看看源碼中關於list類的__iter__()方法的定義:

def __iter__(self, *args, **kwargs): # real signature unknown
    """ Implement iter(self). """
    pass

__iter__()方法調用了iter(self)函數,咱們再來看一下iter()函數的定義:

def iter(source, sentinel=None): # known special case of iter
    """
    iter(iterable) -> iterator
    iter(callable, sentinel) -> iterator
    
    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.
    """
    pass

iter()函數的參數是一個可迭代對象,最終返回一個迭代器

b) for循環會不斷調用迭代器對象的__next__()方法(python2.x中是next()方法),每次循環,都返回迭代器對象的下一個值,直到遇到StopIteration異常。

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

這裏注意:這裏的__next__()方法和內置函數next(iterator, default=None)不是一個東西;(內置函數next(iterator, default=None)也能夠返回迭代器的下一個值)

 c) 而for循環能夠捕獲StopIteration異常並結束循環;

總結一下:

a)for....in iterable,會經過調用iter(iterable)函數(實際上,首先調用的對象的__iter__()方法),返回一個迭代器iterator;

b)每次循環,調用一次對象的__next__(self),直到最後一個值,再次調用會觸發StopIteration

c)for循環捕捉到StopIteration,從而結束循環

上面說了這麼多,到底什麼是迭代器Iterator呢?

任何實現了__iter____next__()(python2中實現next())方法的對象都是迭代器,__iter__返回迭代器自身,__next__返回容器中的下一個值;

既然知道了什麼迭代器,那咱們自定義一個迭代器玩玩:

 1 class Iterator_test(object):
 2     def __init__(self, data):
 3         self.data = data
 4         self.index = len(data)
 5 
 6     def __iter__(self):
 7         return self
 8 
 9     def __next__(self):
10         if self.index <= 0 :
11             raise StopIteration
12         self.index -= 1
13         return self.data[self.index]
14 
15 iterator_winter = Iterator_test('abcde')
16 
17 for item in iterator_winter:
18     print(item)
View Code

如何判斷一個對象是一個迭代器對象呢?兩個方法:

1)經過內置函數next(iterator, default=None),能夠看到next的第一個參數必須是迭代器;因此迭代器也能夠認爲是能夠被next()函數調用的對象

2)經過collection中的Iterator類型判斷

>>> from collections import Iterator
>>>
>>> isinstance([1,2,3], Iterator)
False
>>> isinstance(iter([1,2,3]), Iterator)
True
>>> isinstance([1,2,3].__iter__(), Iterator)
True
>>>

這裏你們會不會有個疑問:

對於迭代器而言,看上去做用的不就是__next__方法嘛,__iter__好像沒什麼卵用,幹嗎還須要__iter__方法呢?

咱們知道,python中迭代是經過for循環實現的,而for循環的循環對象必須是一個可迭代對象Iterable,而Iterable必須是一個實現了__iter__方法的對象;知道爲何須要__iter__魔術方法了吧;

那麼我就是想自定義一個沒有實現__iter__方法的迭代器能夠嗎?能夠,像下面這樣:

class Iterable_test(object):
    def __init__(self, data):
        self.data = data

    def __iter__(self):
        return Iterator_test(self.data)

class Iterator_test(object):
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __next__(self):
        if self.index <= 0 :
            raise StopIteration
        self.index -= 1
        return self.data[self.index]

iterator_winter = Iterable_test('abcde')

for item in iterator_winter:
    print(item)
View Code

先定義一個可迭代對象(包含__iter__方法),而後該對象返回一個迭代器;這樣看上去是否是很麻煩?是否是同時帶有__iter__和__next__魔術方法的迭代器更好呢!

同時,這裏要糾正以前的一個迭代器概念:只要__next__()(python2中實現next())方法的對象都是迭代器;

既然這樣,只須要迭代器Iterator接口就夠了,爲何還要設計可迭代對象Iterable呢?

這個和迭代器不能重複使用有關,下面贊成講解:

3)總結和一些重要知識點

a) 如何複製迭代器

以前在使用enumerate時,咱們說過enumerate對象經過for循環迭代一次後就不能再被迭代:

>>> e = enumerate([1,2,3])
>>>
>>> for x,y in e:
...   print(x,y)
...
0 1
1 2
2 3
>>> for x,y in e:
...   print(x,y)
...
>>>

這是由於enumerate是一個迭代器;

迭代器是一次性消耗品,當循環之後就空了。不能再次使用;經過深拷貝能夠解決;

>>> import copy
>>>
>>> e = enumerate([1,2,3])
>>>
>>> e_deepcopy = copy.deepcopy(e)
>>>
>>> for x,y in e:
...   print(x,y)
...
0 1
1 2
2 3
>>> for x,y in e_deepcopy:
...   print(x,y)
...
0 1
1 2
2 3
>>>

b)爲何不僅保留Iterator的接口而還須要設計Iterable呢?

由於迭代器迭代一次之後就空了,那麼若是list,dict也是一個迭代器,迭代一次就不能再繼續被迭代了,這顯然是反人類的;因此經過__iter__每次返回一個獨立的迭代器,就能夠保證不一樣的迭代過程不會互相影響。而生成器表達式之類的結果每每是一次性的,不能夠重複遍歷,因此直接返回一個Iterator就好。讓Iterator也實現Iterable的兼容就能夠很靈活地選擇返回哪種。

總結說,Iterator實現的__iter__是爲了兼容Iterable的接口,從而讓Iterator成爲Iterable的一種實現。

另外,迭代器是惰性的,只有在須要返回下一個數據時它纔會計算。就像一個懶加載的工廠,等到有人須要的時候纔給它生成值返回,沒調用的時候就處於休眠狀態等待下一次調用。因此,Iterator甚至能夠表示一個無限大的數據流,例如全體天然數。而使用list是永遠不可能存儲全體天然數的。

c)經過__getitem__來實現for循環

前面關於可迭代對象的定義是這樣的:定義了能夠返回一個迭代器的__iter__方法,或者定義了能夠支持下標索引的__getitem__方法,那麼它就是一個可迭代對象。

可是若是對象沒有__iter__,可是實現了__getitem__,會改用下標迭代的方式。

class NoIterable(object):
    def __init__(self, data):
        self.data = data
    def __getitem__(self, item):
        return self.data[item]

no_iter = NoIterable('abcde')
for item in no_iter:
    print(item)
當for發現沒有__iter__可是有__getitem__的時候,會從0開始依次讀取相應的下標,直到發生IndexError爲止, 這是一種舊的迭代方法。iter方法也會處理這種狀況,在不存在__iter__的時候,返回一個下標迭代的iterator對象來代替。
d)一張圖總結迭代器
e)使用迭代器來實現一個斐波那契數列
 1 class Fib(object):
 2     def __init__(self, limit):
 3         self.a, self.b = 0, 1
 4         self.limit = limit
 5 
 6     def __iter__(self):
 7         return self
 8 
 9     def __next__(self):
10         self.a, self.b = self.b, self.a+self.b
11         while self.a > self.limit:
12             raise StopIteration
13         return self.a
14 
15 for n in Fib(1000):
16     print(n)
View Code

 2. 生成器

理解了迭代器之後,生成器就會簡單不少,由於生成器實際上是一種特殊的迭代器。不過這種迭代器更加優雅。它不須要再像上面的類同樣寫__iter__()__next__()方法了,只須要一個yiled關鍵字。 生成器必定是迭代器(反之不成立),所以任何生成器也是以一種懶加載的模式生成值。

語法上說,生成器函數是一個帶yield關鍵字的函數。

調用生成器函數後會獲得一個生成器對象,這個生成器對象實際上就是一個特殊的迭代器,擁有__iter__()__next__()方法

咱們先用一個例子說明一下:

>>> def generator_winter():
...   i = 1
...   while i <= 3:
...     yield i
...     i += 1
...
>>> generator_winter
<function generator_winter at 0x000000000323B9D8>
>>> generator_iter = generator_winter()
>>> generator_iter
<generator object generator_winter at 0x0000000002D9CAF0>
>>>
>>> generator_iter.__next__()
1
>>> generator_iter.__next__()
2
>>> generator_iter.__next__()
3
>>> generator_iter.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

如今解釋一下上面的代碼:

a)首先咱們建立了一個含有yield關鍵字的函數generator_winter,這是一個生成器函數

b)而後,咱們調用了這個生成器函數,而且將返回值賦值給了generator_iter,generator_iter是一個生成器對象;注意generator_iter = generator_winter()時,函數體中的代碼並不會執行,只有顯示或隱示地調用next的時候纔會真正執行裏面的代碼。

c)生成器對象就是一個迭代器,因此咱們能夠調用對象的__next__方法來每次返回一個迭代器的值;迭代器的值經過yield返回;而且迭代完最後一個元素後,觸發StopIteration異常;

既然生成器對象是一個迭代器,咱們就可使用for循環來迭代這個生成器對象:

>>> def generator_winter():
...   i = 1
...   while i <= 3:
...     yield i
...     i += 1
...
>>>
>>> for item in generator_winter():
...   print(item)
...
1
2
3
>>>

咱們注意到迭代器不是使用return來返回值,而是採用yield返回值;那麼這個yield有什麼特別之處呢?

1)yield

咱們知道,一個函數只能返回一次,即return之後,此次函數調用就結束了;

可是生成器函數能夠暫停執行,而且經過yield返回一箇中間值,當生成器對象的__next__()方法再次被調用的時候,生成器函數能夠從上一次暫停的地方繼續執行,直到觸發一個StopIteration

上例中,當執行到yield i後,函數返回i值,而後print這個值,下一次循環,又調用__next__()方法,回到生成器函數,並從yield i的下一句繼續執行;

摘一段<python核心編程>的內容:

生成器的另一個方面甚至更增強力----協同程序的概念。協同程序是能夠運行的獨立函數調用,能夠暫停或者掛起,並從程序離開的地方繼續或者從新開始。在有調用者和(被調用的)協同程序也有通訊。舉例來講,當協同程序暫停時,咱們仍能夠從其中得到一箇中間的返回值,當調用回到程序中時,可以傳入額外或者改變了的參數,可是仍然可以從咱們上次離開的地方繼續,而且全部狀態完整。掛起返回出中間值並屢次繼續的協同程序被稱爲生成器,那就是python的生成真正在作的事情。這些提高讓生成器更加接近一個徹底的協同程序,由於容許值(和異常)能傳回到一個繼續的函數中,一樣的,當等待一個生成器的時候,生成器如今能返回控制,在調用的生成器能掛起(返回一個結果)以前,調用生成器返回一個結果而不是阻塞的等待那個結果返回。

2) 什麼狀況會觸發StopIteration

兩種狀況會觸發StopIteration

a) 若是沒有return,則默認執行到函數完畢時返回StopIteration;

b) 若是在執行過程當中 return,則直接拋出 StopIteration 終止迭代;

c) 若是在return後返回一個值,那麼這個值爲StopIteration異常的說明,不是程序的返回值。

>>> def generator_winter():
...   yield 'hello world'
...   return 'again'
...
>>>
>>> winter = generator_winter()
>>> winter.__next__()
'hello world'
>>> winter.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: again
>>>

3) 生成器的做用

說了這麼多,生成器有什麼用呢?做爲python主要特性之一,這是個極其牛逼的東西,因爲它是惰性的,在處理大型數據時,能夠節省大量內存空間;

當你須要迭代一個巨大的數據集合,好比建立一個有規律的100萬個數字,若是採用列表來存儲訪問,那麼會佔用大量的內存空間;並且若是咱們只是訪問這個列表的前幾個元素,那麼後邊大部分元素佔據的內存空間就白白浪費了;這時,若是採用生成器,則沒必要建立完整的列表,一次循環返回一個但願獲得的值,這樣就能夠大量節省內存空間;

這裏在舉例以前,咱們先介紹一個生成器表達式(相似於列表推導式,只是把[]換成()),這樣就建立了一個生成器。

>>> gen = (x for x in range(10))
>>> gen
<generator object <genexpr> at 0x0000000002A923B8>
>>>

生成器表達式的語法以下:

(expr for iter_var in iterable if cond_expr)

用生成器來實現斐波那契數列

1 def fib(n):
2     a, b = 0, 1
3     while b <= n:
4         yield b
5         a, b = b, a+b
6 
7 f = fib(10)
8 for item in f:
9     print(item)
View Code

 4)生成器方法

直接看生成器源代碼

class __generator(object):
    '''A mock class representing the generator function type.'''
    def __init__(self):
        self.gi_code = None
        self.gi_frame = None
        self.gi_running = 0

    def __iter__(self):
        '''Defined to support iteration over container.'''
        pass

    def __next__(self):
        '''Return the next item from the container.'''
        pass

    def close(self):
        '''Raises new GeneratorExit exception inside the generator to terminate the iteration.'''
        pass

    def send(self, value):
        '''Resumes the generator and "sends" a value that becomes the result of the current yield-expression.'''
        pass

    def throw(self, type, value=None, traceback=None):
        '''Used to raise an exception inside the generator.'''
        pass

首先看到了生成器是自帶__iter__和__next__魔術方法的;

a)send

生成器函數最大的特色是能夠接受外部傳入的一個變量,並根據變量內容計算結果後返回。這是生成器函數最難理解的地方,也是最重要的地方,協程的實現就全靠它了

看一個小貓吃魚的例子:

def cat():
    print('我是一隻hello kitty')
    while True:
        food = yield
        if food == '魚肉':
            yield '好開心'
        else:
            yield '不開心,人家要吃魚肉啦'

中間有個賦值語句food = yield,能夠經過send方法來傳參數給food,試一下:

 狀況1)

miao = cat()    #只是用於返回一個生成器對象,cat函數不會執行
print(''.center(50,'-'))
print(miao.send('魚肉'))

結果:

Traceback (most recent call last):
--------------------------------------------------
  File "C:/Users//Desktop/Python/cnblogs/subModule.py", line 67, in <module>
    print(miao.send('魚肉'))
TypeError: can't send non-None value to a just-started generator

看到了兩個信息:

a)miao = cat() ,只是用於返回一個生成器對象,cat函數不會執行

b)can't send non-None value to a just-started generator;不能給一個剛建立的生成器對象直接send值

改一下

狀況2)

miao = cat()
miao.__next__()
print(miao.send('魚肉'))

結果:

我是一隻hello kitty
好開心

沒毛病,那麼到底send()作了什麼呢?send()的幫助文檔寫的很清楚,'''Resumes the generator and "sends" a value that becomes the result of the current yield-expression.''';能夠看到send依次作了兩件事:

a)回到生成器掛起的位置,繼續執行

b)並將send(arg)中的參數賦值給對應的變量,若是沒有變量接收值,那麼就只是回到生成器掛起的位置

可是,我認爲send還作了第三件事:

c)兼顧__next__()做用,掛起程序並返回值,因此咱們在print(miao.send('魚肉'))時,纔會看到'好開心';其實__next__()等價於send(None)

因此當咱們嘗試這樣作的時候:

 1 def cat():
 2     print('我是一隻hello kitty')
 3     while True:
 4         food = yield
 5         if food == '魚肉':
 6             yield '好開心'
 7         else:
 8             yield '不開心,人家要吃魚肉啦'
 9 
10 miao = cat()
11 print(miao.__next__())
12 print(miao.send('魚肉'))
13 print(miao.send('骨頭'))
14 print(miao.send('雞肉'))

就會獲得這個結果:

我是一隻hello kitty
None
好開心
None
不開心,人家要吃魚肉啦

咱們按步驟分析一下:

a)執行到print(miao.__next__()),執行cat()函數,print了」我是一隻hello kitty」,而後在food = yield掛起,並返回了None,打印None

b)接着執行print(miao.send('魚肉')),回到food = yield,並將'魚肉’賦值給food,生成器函數恢復執行;直到運行到yield '好開心',程序掛起,返回'好開心',並print'好開心'

c)接着執行print(miao.send('骨頭')),回到yield '好開心',這時沒有變量接收參數'骨頭',生成器函數恢復執行;直到food = yield,程序掛起,返回None,並print None

d)接着執行print(miao.send('雞肉')),回到food = yield,並將'雞肉’賦值給food,生成器函數恢復執行;直到運行到yield'不開心,人家要吃魚肉啦',程序掛起,返回'不開心,人家要吃魚肉啦',,並print '不開心,人家要吃魚肉啦'

大功告成;那咱們優化一下代碼:

 1 def cat():
 2     msg = '我是一隻hello kitty'
 3     while True:
 4         food = yield msg
 5         if food == '魚肉':
 6             msg = '好開心'
 7         else:
 8             msg = '不開心,人家要吃魚啦'
 9 
10 miao = cat()
11 print(miao.__next__())
12 print(miao.send('魚肉'))
13 print(miao.send('雞肉'))

咱們再看一個更實用的例子,一個計數器

def counter(start_at = 0):
    count = start_at
    while True:
        val = (yield count)
        if val is not None:
            count = val
        else:
            count += 1

count = counter(5)
print(count.__next__())
print(count.__next__())
print(count.send(0))
print(count.__next__())
print(count.__next__())

結果:

5
6
0
1
2

b)close

幫助文檔:'''Raises new GeneratorExit exception inside the generator to terminate the iteration.'''

手動關閉生成器函數,後面的調用會直接返回StopIteration異常

>>> def gene():
...   while True:
...     yield 'ok'
...
>>> g = gene()
>>> g.__next__()
'ok'
>>> g.__next__()
'ok'
>>> g.close()
>>> g.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

在close之後再執行__next__會觸發StopIteration異常

c)throw

用來向生成器函數送入一個異常,throw()後直接拋出異常並結束程序,或者消耗掉一個yield,或者在沒有下一個yield的時候直接進行到程序的結尾。

>>> def gene():
...   while True:
...     try:
...       yield 'normal value'
...     except ValueError:
...       yield 'we got ValueError here'
...     except TypeError:
...       break
...
>>> g = gene()
>>> print(g.__next__())
normal value
>>> print(g.__next__())
normal value
>>> print(g.throw(ValueError))
we got ValueError here
>>> print(g.__next__())
normal value
>>> print(g.throw(TypeError))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

 5)經過yield實現單線程狀況下的異步併發效果

def consumer(name):
    print('%s準備吃包子了' % name)
    while True:
        baozi_name = yield
        print('[%s]來了,被[%s]吃了'% (baozi_name, name))

def producer(*name):
    c1 = consumer(name[0])
    c2 = consumer(name[1])
    c1.__next__()
    c2.__next__()
    for times in range(5):
        print('作了兩個包子')
        c1.send('豆沙包%s'%times)
        c2.send('菜包%s'%times)

producer('winter', 'elly')

效果:

winter準備吃包子了
elly準備吃包子了
作了兩個包子
[豆沙包0]來了,被[winter]吃了
[菜包0]來了,被[elly]吃了
作了兩個包子
[豆沙包1]來了,被[winter]吃了
[菜包1]來了,被[elly]吃了
作了兩個包子
[豆沙包2]來了,被[winter]吃了
[菜包2]來了,被[elly]吃了
作了兩個包子
[豆沙包3]來了,被[winter]吃了
[菜包3]來了,被[elly]吃了
作了兩個包子
[豆沙包4]來了,被[winter]吃了
[菜包4]來了,被[elly]吃了

建立了兩個獨立的生成器,頗有趣,很吊;

 6)補充幾個小例子:

a)使用生成器建立一個range

def range(n):
    count = 0
    while count < n:
        yield count
        count += 1

b ) 使用生成器監聽文件輸入

def fileTail(filename):
    with open(filename) as f:
        while True:
            tail = f.readline()
            if line:
                yield tail
            else:
                time.sleep(0.1)

c)計算移動平均值

def averager(start_with = 0):
    count = 0
    aver = start_with
    total = start_with
    while True:
        val = yield aver
        total += val
        count += 1
        aver = total/count

有個弊端,須要經過__next__或next()初始化一次,經過預激解決

d)預激計算移動平均值

def init(f):
    def wrapper(start_with = 0):
        g_aver = f(start_with)
        g_aver.__next__()
        return g_aver
    return wrapper

@init
def averager(start_with = 0):
    count = 0
    aver = start_with
    total = start_with
    while True:
        val = yield aver
        total += val
        count += 1
        aver = total/count

 e)讀取文件字符數最多的行的字符數

最傳統的寫法:

def longestLine(filename):
    with open(filename, 'r', encoding='utf-8') as f:
        alllines = [len(x.strip()) for x in f]
        return max(alllines)

使用生成器之後的寫法:

def longestLine(filename):
    return max(len(x.strip()) for x in open(filename))

f)多生成器迭代

>>> g = (i for i in range(5))
>>> for j in g:
...   print(j)
...
0
1
2
3
4
>>> for j in g:
...   print(j)
...
>>>

由於for j in g, 每次循環執行一次g.__next__();直到結束,觸發StopIteration;

主意下面結果的輸出:

>>> g = (i for i in range(4))
>>> g1 = (x for x in g)
>>> g2 = (y for y in g1)
>>>
>>> print(list(g1))
[0, 1, 2, 3]
>>> print(list(g2))
[]
>>>

爲何print(list(g2))爲空呢?理一下,否則會亂:

看下面的代碼:

 1 def g():
 2     print('1.1')
 3     for i in range(2):
 4         print('1.2')
 5         yield i
 6         print('1.3')
 7 
 8 def g1():
 9     print('2.1')
10     for x in s:
11         print('2.2')
12         yield x
13         print('2.3')
14 
15 def g2():
16     print('3.1')
17     for y in s1:
18         print('3.2')
19         yield y
20         print('3.3')
21 
22 s = g()
23 s1 = g1()
24 s2 = g2()
25 print('start first list')
26 print(list(s1))
27 print('start second list')
28 print(list(s2))

結果:

 1 start first list
 2 2.1
 3 1.1
 4 1.2
 5 2.2
 6 2.3
 7 1.3
 8 1.2
 9 2.2
10 2.3
11 1.3
12 [0, 1]
13 start second list
14 3.1
15 []

注意第11行以後,g觸發了StopIteration,被for x in s捕捉,即不能繼續s.__next__()了;一樣的g1觸發StopIteration,被list捕捉,即不能繼續s1.__next__()了;因而打印[0,1]

當進行print(list(s2))時,執行s2.__next__(),停留在代碼的第17行for y in s1,可是這是不能繼續s1.__next__()了;因而直接觸發了StopIteration;結果爲[]

再看一個有意思的輸出:

def add(n,i):
    return n+i

g = (i for i in range(4))

for n in [1,10]:
    g = (add(n,i) for i in g)

print(list(g))

輸出爲:

[20, 21, 22, 23]

其實上面的代碼翻譯以下:

def add(n,i):
    return n+i

def g1():
    for i in g:
        yield add(n,i)

def g2():
    for i in s1:
        yield add(n,i)

n = 1
s1 = g1()
n = 10
s2 = g2()
print(list(s2))

最終n用的是10,

相關文章
相關標籤/搜索