Python可迭代對象/迭代器/生成器

Python可迭代對象/迭代器/生成器

概述

迭代是數(shù)據(jù)處理的基石.

掃描內(nèi)存中放不下的數(shù)據(jù)集時, 需要找到一種惰性獲取數(shù)據(jù)的方式, 即需要按需一次獲取一個數(shù)據(jù)項. 這就是迭代器模式(Iterator pattern).

所有生成器都是迭代器, 因為生成器完全實現(xiàn)了迭代器接口.

在Python中所有集合都可迭代的. 在Python語言內(nèi)部, 迭代器用于支持:

  • for循環(huán)
  • 構(gòu)建和擴(kuò)展集合類型
  • 逐行遍歷文本文件
  • 列表推導(dǎo)/字典推導(dǎo)/集合推導(dǎo)
  • 元組拆包
  • 調(diào)用函數(shù)時, 使用*拆包實參

2.序列為何可以迭代?

解釋器需要迭代對象x時, 會自動調(diào)用iter(x)函數(shù).

內(nèi)置的iter函數(shù)有以下作用:

  1. 檢查對象是否實現(xiàn)了__iter__方法, 如果實現(xiàn)了就調(diào)用它, 獲取一個迭代器;
  2. 如果該對象沒有實現(xiàn)__iter__方法, 但是實現(xiàn)了__getitem__方法, Python會創(chuàng)建一個迭代器, 嘗試按順序(從索引0開始)獲取元素;
  3. 如果嘗試失敗, Python會拋出TypeError異常, 通常會提示C object is not iterable(C對象不可迭代), 其中C是目標(biāo)對象所屬的類.

由上可知: 所有的序列都可迭代的原因---都實現(xiàn)了__getitem__方法.

標(biāo)準(zhǔn)的序列也都實現(xiàn)了__iter__方法, 因此我們也應(yīng)該這么做. 之所以對__getitem__方法做特殊處理,是為了向后兼容.

鴨子類型(duck typing)的極端形式: 不僅要實現(xiàn)特殊的__iter__方法,還要實現(xiàn)__getitem__方法, 而且__getitem__方法的參數(shù)是從0開始的整數(shù)(int), 才認(rèn)為對象是可迭代的.

白鵝類型(goose-typing)理論中: 如果實現(xiàn)了__iter__方法, 那么就認(rèn)為對象是可迭代的.

檢查對象x是否可迭代, 最準(zhǔn)確的方法: 調(diào)用iter(x)函數(shù), 如果不可迭代將拋出TypeError異常, 再來處理此異常.

可迭代的對象與迭代器的對比

可迭代的對象: 使用iter內(nèi)置函數(shù)可以獲取迭代器的對象.

  • 如果對象實現(xiàn)了能返回迭代器__iter__方法.
  • 序列對象.
  • 實現(xiàn)了__getitem__方法, 而且其參數(shù)是從零開始的索引.

可迭代對象與迭代器關(guān)系: Python從可迭代對象中獲取迭代器.

StopIteration異常表明迭代器到頭了.

Python語言內(nèi)部會處理for循環(huán)和其他迭代器上下文(如列表推導(dǎo)/元組拆包,等等)中的StopIteration異常.

標(biāo)準(zhǔn)迭代器接口:

  • __next__: 返回下一個可用的元素, 如果沒有元素了, 拋出StopIteration異常.
  • __iter__: 返回self, 即迭代器實例本身. 以便在應(yīng)該使用可迭代對象的地方使用迭代器, 例如在for循環(huán)中.

我們應(yīng)該避免調(diào)用類似上述這種特殊方法, 使用next()即可.

檢查對象x是否是迭代器最好的辦法: 調(diào)用isinstance(x, abc.Iterator). 得益于Iterator.__subclasshook__方法, 即使對象x所屬的類不是Iterator類的真實子類或虛擬子類, 也能這樣檢查.

迭代器: 實現(xiàn)了無參數(shù)的__next__方法, 返回序列中的下一個元素; 如果沒有元素了, 那么拋出StopIteration異常.

Python中的迭代器實現(xiàn)了__iter__方法, 因此迭代器也可以迭代.

因為內(nèi)置的iter()函數(shù)對序列做特殊處理. 接下來實現(xiàn)標(biāo)準(zhǔn)的迭代器協(xié)議

3.典型迭代器

# -*- coding: UTF-8 -*-
import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        # 實現(xiàn)此方法: 表明此類可以迭代. 根據(jù)迭代器協(xié)議,__iter__方法實例并返回一個迭代器
        return SentenceIterator(self.words)


class SentenceIterator:

    def __init__(self, words):
        self.words = words  # 迭代器類引用單詞列表
        self.index = 0  # index用于確定下一個要獲取的單詞

    def __next__(self):
        try:
            word = self.words[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return word

    def __iter__(self):
        return self


if __name__ == '__main__':
    s = Sentence('"The time has come, " the Walrus said,')
    print(s)
    for word in s:
        print(word)
    print(list(s))

注意: SentenceIterator類的大多數(shù)代碼都在處理迭代器內(nèi)部狀態(tài).

可迭代對象實現(xiàn)了__iter__方法, 每次都會實例化一個新的迭代器.

迭代器要實現(xiàn)__next__方法, 調(diào)用next()函數(shù)時返回單個元素, 還要實現(xiàn)__iter__方法, 返回迭代器本身.

因此: 迭代器可以迭代, 但是可迭代的對象本身不是迭代器.

反模式: 在Sentence類中實現(xiàn)__next__方法, 讓其實例既是可迭代的對象, 也是自身的迭代器.

可迭代的對象一定不能是自身的迭代器.

也就是: 可迭代的對象必須實現(xiàn)__iter__方法, 但不能實現(xiàn)__next__方法.

另一方面: 迭代器應(yīng)該可以一直迭代. 迭代器的__iter__方法應(yīng)該返回自身.

迭代器模式可用來:

  • 訪問一個聚合對象的內(nèi)容而無需暴露它的內(nèi)部表示
  • 支持對聚合對象的多種遍歷
  • 為遍歷不同的聚合結(jié)構(gòu)提供一個統(tǒng)一的接口(即支持多態(tài)迭代)

為了"支持多種遍歷", 必須能從同一個可迭代的實例中獲取多個獨立的迭代器, 而且各個迭代器要能維護(hù)自身的 內(nèi)部狀態(tài), 因此這一模式正確的實現(xiàn)方法是: 每次調(diào)用iter(my_iterable)都新建一個獨立的迭代器.

這就是定義SentenceIterator類的原因.

4.生成器函數(shù)

# -*- coding: UTF-8 -*-
import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        """生成器函數(shù)"""
        for word in self.words:
            yield word
        return  # 此return不是必須的.可以直接不寫,直接返回None.不管有沒有,都不會拋出StopIteration異常.
    

if __name__ == '__main__':
    s = Sentence('"The time has come, " the Walrus said,')
    print(s)
    for word in s:
        print(word)
    print(list(s))

生成器函數(shù): 只要Python函數(shù)的定義體中有yield關(guān)鍵字, 該函數(shù)就是生成器函數(shù).

調(diào)用生成器函數(shù)時, 會返回一個生成器對象. 即: 生成器函數(shù)是生成器工廠.

執(zhí)行過程: 生成器函數(shù)會創(chuàng)建一個生成器對象, 包裝生成器函數(shù)的定義體. 把生成器傳給next()函數(shù)時, 生成器函數(shù)會向前, 執(zhí)行函數(shù)定義體中的下一個yield語句, 返回產(chǎn)出的值, 并在函數(shù)定義體的當(dāng)前位置暫停. 最終, 函數(shù)定義體返回時, 外層的生成器對象會拋出StopIteration異常---與迭代器協(xié)議一致.

In [1]: def gen_123(): 
   ...:     yield 1 
   ...:     yield 2 
   ...:     yield 3 
   ...:                                                                                                                                   

In [2]: gen_123                                                                                                                           
Out[2]: <function __main__.gen_123()>

In [3]: gen_123()                                                                                                                         
Out[3]: <generator object gen_123 at 0x10ce05c50>

In [4]: for i in gen_123(): 
   ...:     print(i) 
   ...:                                                                                                                                   
1
2
3

In [5]: g = gen_123()                                                                                                                     

In [6]: next(g)                                                                                                                           
Out[6]: 1

In [7]: next(g)                                                                                                                           
Out[7]: 2

In [8]: next(g)                                                                                                                           
Out[8]: 3

In [9]: next(g)                                                                                                                           
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-9-e734f8aca5ac> in <module>
----> 1 next(g)

StopIteration: 

In [10]:       

生成器不會以常規(guī)的方式"返回"值: 生成器函數(shù)定義體中的return語句會觸發(fā)生成器對象拋出StopIteration異常.

In [10]: def gen_AB(): 
    ...:     print("start") 
    ...:     yield 'A' 
    ...:     print("continue") 
    ...:     yield 'B' 
    ...:     print("end.") 
    ...:                                                                                                                                  

In [11]: for c in gen_AB(): 
    ...:     print('-->', c) 
    ...:                                                                                                                                  
start
--> A
continue
--> B
end.

In [12]: 

for機(jī)制的作用與g = iter(gen_AB())一樣, 用于獲取生成器對象, 然后每次迭代時調(diào)用next(g).

5.惰性實現(xiàn)

設(shè)計Iterator接口時,考慮到了惰性: next(Iterator)一次生成一個元素.

惰性求值(lazy evaluation)

及早求值(eager evaluation)

# -*- coding: UTF-8 -*-
import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        # finditer函數(shù)構(gòu)建一個迭代器,包含self.text中匹配RE_WORD的單詞,產(chǎn)出MatchObject實例
        for match in RE_WORD.finditer(self.text):
            # match.group()方法從MatchObject實例中提取匹配正則表達(dá)式的具體文本
            yield match.group()


if __name__ == '__main__':
    s = Sentence('"The time has come, " the Walrus said,')
    print(s)
    for word in s:
        print(word)
    print(list(s))

6.生成器表達(dá)式

生成器表達(dá)式可以理解為列表推導(dǎo)的惰性版本: 不會馬上創(chuàng)建列表, 而是返回一個生成器, 按需惰性生成元素.

即: 如果列表推導(dǎo)是制造列表的工廠, 那么生成器表達(dá)式就是制造生成器的工廠.

In [1]: def gen_AB(): 
   ...:     print('start') 
   ...:     yield 'A' 
   ...:     print('continue') 
   ...:     yield 'B' 
   ...:     print('end.') 
   ...:                                                                                                                                   

In [2]: res1 = [x*3 for x in gen_AB()]                                                                                                    
start
continue
end.

In [3]: for i in res1: 
   ...:     print('-->', i) 
   ...:                                                                                                                                   
--> AAA
--> BBB

In [4]: res2 = (x*3 for x in gen_AB())                                                                                                    

In [5]: res2                                                                                                                              
Out[5]: <generator object <genexpr> at 0x110f66258>

In [6]: for i in res2: # for循環(huán)每次迭代時,會隱式調(diào)用next(res2),前進(jìn)到gen_AB函數(shù)的下一個yield語句
   ...:     print('-->', i) 
   ...:                                                                                                                                   
start
--> AAA
continue
--> BBB
end.

由上可知,生成器表達(dá)式會產(chǎn)出生成器, 因此可以使用生成器表達(dá)式進(jìn)一步減少Sentence類的代碼, 如下所示:

# -*- coding: UTF-8 -*-
import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence:

    def __init__(self, text):
        self.text = text

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        return (match.group() for match in RE_WORD.finditer(self.text))


if __name__ == '__main__':
    s = Sentence('"The time has come, " the Walrus said,')
    print(s)
    for word in s:
        print(word)
    print(list(s))

生成器表達(dá)式是語法糖: 完全可以替換成生成器函數(shù), 不過有時使用生成器函數(shù)更便利.

7.何時使用生成器表達(dá)式

如果生成器表達(dá)式要分成多行寫, 可以選擇定義生成器函數(shù), 以便提高可讀性.

而且, 生成器函數(shù)有名稱, 可以重用.

句法提示: 如果函數(shù)或構(gòu)造方法只有一個參數(shù), 傳入生成器表達(dá)式時不用寫一對調(diào)用函數(shù)的括號, 再寫一對括號圍住生成器表達(dá)式, 只寫一對括號就行了. 然而, 如果生成器表達(dá)式后面還有其他參數(shù), 那么必須使用括號圍住,否則會拋出SyntaxError異常.

8.等差數(shù)列生成器

典型的迭代器模式作用很簡單-----遍歷數(shù)據(jù)結(jié)構(gòu).

不過, 即便不是從集合中獲取元素, 而是獲取序列中即時生成的下一個值時, 也用得到這種基于方法的標(biāo)準(zhǔn)接口.

例如: 內(nèi)置的range函數(shù)用于生成有窮整數(shù)等差數(shù)列(Arithmetic Progression, AP); itertools.count函數(shù)用于生成無窮等差數(shù)列.

# -*- coding: UTF-8 -*-


class ArithmeticProgression:

    def __init__(self, begin, step, end=None):
        self.begin = begin
        self.step = step
        self.end = end

    def __iter__(self):
        # 將self.begin的值賦值給result,不過先強(qiáng)制轉(zhuǎn)換成前面的加法算式得到的類型.
        result = type(self.begin + self.step)(self.begin)
        forever = self.end is None
        index = 0
        while forever or result < self.end:
            yield result
            index += 1
            result = self.begin + self.step * index
            
            
if __name__ == '__main__':
    ap = ArithmeticProgression(0, 1, 3)
    print(list(ap))

    ap = ArithmeticProgression(1, .5, 3)
    print(list(ap))

    ap = ArithmeticProgression(0, 1/3, 1)
    print(list(ap))

    from fractions import Fraction
    ap = ArithmeticProgression(0, Fraction(1, 3), 1)
    print(list(ap))

    from decimal import Decimal
    ap = ArithmeticProgression(0, Decimal('.1'), .3)
    print(list(ap))

上面這個類只是演示了如何使用生成器函數(shù)實現(xiàn)特殊的__iter__方法.

然而, 如果一個類只是為了構(gòu)建生成器而去實現(xiàn)__iter__方法, 還不如使用生成器函數(shù).

畢竟, 生成器函數(shù)是制造生成器的工廠.

# -*- coding: UTF-8 -*-


def aritprog_gen(begin, step, end=None):
    result = type(begin + step)(begin)
    forever = end is None
    index = 0
    while forever or result < end:
        yield result
        index += 1
        result = begin + step * index


if __name__ == '__main__':
    ap = aritprog_gen(0, 1, 3)
    print(list(ap))

    ap = aritprog_gen(1, .5, 3)
    print(list(ap))

    ap = aritprog_gen(0, 1/3, 1)
    print(list(ap))

    from fractions import Fraction
    ap = aritprog_gen(0, Fraction(1, 3), 1)
    print(list(ap))

    from decimal import Decimal
    ap = aritprog_gen(0, Decimal('.1'), .3)
    print(list(ap))

上述即使用生成器函數(shù)來實現(xiàn)的.

使用itertools模塊生成等差數(shù)列

itertools.count(start, step)示例:

In [7]: import itertools                                                                                                                  

In [8]: gen = itertools.count(1, .5)                                                                                                      

In [9]: next(gen)                                                                                                                         
Out[9]: 1

In [10]: next(gen)                                                                                                                        
Out[10]: 1.5

In [11]: next(gen)                                                                                                                        
Out[11]: 2.0

In [12]: next(gen)                                                                                                                        
Out[12]: 2.5

然而, itertools.count函數(shù)從不停止. 如果調(diào)用list(count()), Python會創(chuàng)建一個特別大的列表, 超出可用內(nèi)存, 在調(diào)用失敗之前, 電腦會瘋狂地運(yùn)轉(zhuǎn).

itertools.takewhile函數(shù)則不同. 它會生成一個使用另一個生成器的生成器, 在指定的條件計算結(jié)果為False時停止. 因此, 可以把這兩個函數(shù)結(jié)合在一起使用. 如下所示:

In [13]: gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5))                                                               

In [14]: list(gen)                                                                                                                        
Out[14]: [1, 1.5, 2.0, 2.5]

利用takewhilecount函數(shù), 編寫的代碼流暢而簡短, 如下所示:

# -*- coding: UTF-8 -*-
import itertools


def aritprog_gen(begin, step, end=None):
    first = type(begin + step)(begin)
    ap_gen = itertools.count(first, step)
    if end is not None:
        ap_gen = itertools.takewhile(lambda n: n < end, ap_gen)
    return ap_gen


if __name__ == '__main__':
    ap = aritprog_gen(0, 1, 3)
    print(list(ap))

    ap = aritprog_gen(1, .5, 3)
    print(list(ap))

    ap = aritprog_gen(0, 1/3, 1)
    print(list(ap))

    from fractions import Fraction
    ap = aritprog_gen(0, Fraction(1, 3), 1)
    print(list(ap))

    from decimal import Decimal
    ap = aritprog_gen(0, Decimal('.1'), .3)
    print(list(ap))

上述示例中的aritprog_gen不是生成器函數(shù), 因為定義體中并沒有yield關(guān)鍵字.

但是它會返回一個生成器, 因此和其他生成器函數(shù)一樣, 也是生成器工廠函數(shù).

注意: 實現(xiàn)生成器時要知道標(biāo)準(zhǔn)庫中有什么可用, 否則很可能重復(fù)造輪子.

9.標(biāo)準(zhǔn)庫中的生成器函數(shù)

第一組是用于過濾的生成器函數(shù):從輸入的可迭代對象中產(chǎn)出元素的子集,而且不修改元素本身.

下表中的大多數(shù)函數(shù)都接受一個斷言參數(shù)(predicate). 這個參數(shù)是個布爾函數(shù), 有一個參數(shù), 會應(yīng)用到輸入中的每個元素上, 用于判斷元素是否包含在輸出中.

模塊 函數(shù) 說明
itertools compress(it, selector_it) 并行處理兩個可迭代的對象; 如果selector_it中的元素是真值, 產(chǎn)出it中對應(yīng)的元素.
itertools dropwhile(predicate, it) 處理it, 跳過predicate的計算結(jié)果為真值的元素, 然后產(chǎn)出剩下的各個元素(不再進(jìn)一步檢查)
內(nèi)置 filter(predicate, it) it中的每個元素傳給predicate, 如果返回真值, 那么產(chǎn)出對應(yīng)的元素; 如果predicate為None, 那么只產(chǎn)出真值元素
itertools filterfalse(predicate, it) filter函數(shù)類似.不過是相反的: predicate返回假值時產(chǎn)出對應(yīng)的元素
itertools islice(it, stop)islice(it, start, stop, step=1) 產(chǎn)出it的切片, 作用類似于s[:stop]s[start:stop:step], 不過it可以是任何可迭代的對象, 而且這個函數(shù)實現(xiàn)的是惰性操作
itertools takewhile(predicate, it) predicate返回真值時產(chǎn)出對應(yīng)的元素, 然后立即停止, 不再繼續(xù)檢查.

示例如下:

In [15]: def vowel(c): 
    ...:     return c.lower() in 'aeiou' 
    ...:                                                                                                                                  

In [16]: list(filter(vowel, 'Aardvark'))                                                                                                  
Out[16]: ['A', 'a', 'a']

In [17]: import itertools                                                                                                                 

In [18]: list(itertools.filterfalse(vowel, 'Aardvark'))                                                                                   
Out[18]: ['r', 'd', 'v', 'r', 'k']

In [19]: list(itertools.dropwhile(vowel, 'Aardvark'))                                                                                     
Out[19]: ['r', 'd', 'v', 'a', 'r', 'k']

In [20]: list(itertools.takewhile(vowel, 'Aardvark'))                                                                                     
Out[20]: ['A', 'a']

In [21]: list(itertools.compress('Aardvark', (1,0,1,1,0,1)))                                                                              
Out[21]: ['A', 'r', 'd', 'a']

In [22]: list(itertools.islice('Aardvark', 4, 7))                                                                                         
Out[22]: ['v', 'a', 'r']

In [23]: list(itertools.islice('Aardvark', 1, 7, 2))                                                                                      
Out[23]: ['a', 'd', 'a']

下一組是用于映射的生成器函數(shù): 在輸入的單個可迭代對象(mapstarmap函數(shù)處理多個可迭代的對象)中的各個元素上做計算, 然后返回結(jié)果.

下表中的生成器函數(shù)會從輸入的可迭代對象中的各個元素中產(chǎn)出一個元素. 如果輸入來自多個可迭代的對象, 第一個可迭代的對象到頭后就停止輸出.

模塊 函數(shù) 說明
itertools accumulate(it, [func] 產(chǎn)出累積的總和; 如果提供了func, 那么把前兩個元素傳給它, 然后把計算結(jié)果和下一個元素傳給它, 以此類推, 最后產(chǎn)出結(jié)果.
內(nèi)置 enumerate(iterable, start=0) 產(chǎn)出由兩個元素組成的元組, 結(jié)構(gòu)是(index, item), 其中indexstart開始計數(shù), item則從iterable中獲取
內(nèi)置 map(func, it1, [it2,...,itN] it中的各個元素傳給func, 產(chǎn)出結(jié)果; 如果傳入N個可迭代的對象, 那么func必須能接受N個參數(shù), 而且要并行處理各個可迭代的對象
itertools starmap(func, it) it中的各個元素傳給func, 產(chǎn)出結(jié)果; 輸入的可迭代對象應(yīng)該產(chǎn)出可迭代的元素iit, 然后以func(*iit)這種形式調(diào)用func

itertools.accumulate示例:

In [24]: sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]                                                                                          

In [25]: import itertools                                                                                                                 

In [26]: list(itertools.accumulate(sample))   # 計算總和                                                                                            
Out[26]: [5, 9, 11, 19, 26, 32, 35, 35, 44, 45]

In [27]: list(itertools.accumulate(sample, min))   # 計算最小值                                                                                       
Out[27]: [5, 4, 2, 2, 2, 2, 2, 0, 0, 0]

In [28]: list(itertools.accumulate(sample, max))   # 計算最大值                                                                                        
Out[28]: [5, 5, 5, 8, 8, 8, 8, 8, 9, 9]

In [29]: import operator                                                                                                                  

In [30]: list(itertools.accumulate(sample, operator.mul))  # 計算乘積                                                                               
Out[30]: [5, 20, 40, 320, 2240, 13440, 40320, 0, 0, 0]

In [31]: list(itertools.accumulate(range(1, 11), operator.mul))   # 計算各數(shù)階乘                                                                         
Out[31]: [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

映射生成器函數(shù)示例:

In [32]: list(enumerate('albatroz', 1))  # 以1開始,為單詞中的字母編號                                                                                                  
Out[32]: 
[(1, 'a'),
 (2, 'l'),
 (3, 'b'),
 (4, 'a'),
 (5, 't'),
 (6, 'r'),
 (7, 'o'),
 (8, 'z')]

In [33]: import operator                                                                                                                  

In [34]: list(map(operator.mul, range(11), range(11)))  # 從0到10,計算各個數(shù)的平方                                                                                  
Out[34]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

In [35]: list(map(operator.mul, range(11), [2, 4, 8]))  # 元素最少的可迭代對象到頭后停止                                                                                  
Out[35]: [0, 4, 16]

In [36]: list(map(lambda x, y: (x, y), range(11), [2, 4, 8])) # 相當(dāng)于zip函數(shù)                                                                            
Out[36]: [(0, 2), (1, 4), (2, 8)]

In [37]: list(itertools.starmap(operator.mul, enumerate('albatroz', 1)))                                                                  
Out[37]: ['a', 'll', 'bbb', 'aaaa', 'ttttt', 'rrrrrr', 'ooooooo', 'zzzzzzzz']

In [38]: sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]                                                                                          

In [39]: list(itertools.starmap(lambda a, b: b/a, enumerate(itertools.accumulate(sample), 1)))                                            
Out[39]: 
[5.0,
 4.5,
 3.6666666666666665,
 4.75,
 5.2,
 5.333333333333333,
 5.0,
 4.375,
 4.888888888888889,
 4.5]

下面一組是用于合并的生成器函數(shù), 這些函數(shù)都從輸入的多個可迭代對象中產(chǎn)出元素.

  • chainchain.from_iterable按順序(一個接一個)處理輸入的可迭代對象
  • product/zipzip_longest并行處理輸入的各個可迭代對象.
模塊 函數(shù) 說明
itertools chain(it1, ..., itN) 先產(chǎn)出it1中的所有元素, 然后產(chǎn)出it2中的所有元素, 以此類推, 無縫銜接在一起
itertools chain.from_iterable(it) 產(chǎn)出it生成的各個可迭代對象中的元素, 一個接一個, 無縫銜接; it應(yīng)該產(chǎn)出可迭代的元素, 例如可迭代的對象列表
itertools product(it1, ..., itN, repeat=1) 計算笛卡爾積: 從輸入的各個可迭代對象中獲取元素, 合并成由N個元素組成的數(shù)組, 與嵌套的for循環(huán)一樣; repeat指明重復(fù)處理多少次輸入的可迭代對象
內(nèi)置 zip(it1, ..., itN) 并行從輸入的各個可迭代對象中獲取元素, 產(chǎn)出由N個元素組成的元組, 只要有一個可迭代對象到頭了, 就默默停止
itertools zip_longest(it1, ..., itN, fillvalue=None) 并行從輸入的各個可迭代對象中獲取元素, 產(chǎn)出由N個元素組成的元組, 等到最長的可迭代對象到頭后停止, 空缺的值使用fillvalue填充.

zip函數(shù)的名稱出自zip fastenerzipper(拉鏈, 與ZIP壓縮沒有關(guān)系).

chainzip生成器函數(shù)及其同胞使用示例:

In [40]: import itertools                                                                                                                 

In [41]: list(itertools.chain('ABC', range(2))) # 通常傳入兩個或多個可迭代對象                                                                                          
Out[41]: ['A', 'B', 'C', 0, 1]

In [42]: list(itertools.chain(enumerate('ABC')))  # 如果僅傳入一個,則沒什么效果                                                                                        
Out[42]: [(0, 'A'), (1, 'B'), (2, 'C')]

In [43]: list(itertools.chain.from_iterable(enumerate('ABC')))                                                                          
Out[43]: [0, 'A', 1, 'B', 2, 'C']

In [44]: list(zip('ABC', range(5)))  # 常用于將兩個可迭代對象合并成一系列由兩個元素組成的元組                                                                                                      
Out[44]: [('A', 0), ('B', 1), ('C', 2)]

In [45]: list(zip('ABC', range(5), [10, 20, 30, 40]))                                                                                     
Out[45]: [('A', 0, 10), ('B', 1, 20), ('C', 2, 30)]

In [46]: list(itertools.zip_longest('ABC', range(5)))                                                                                     
Out[46]: [('A', 0), ('B', 1), ('C', 2), (None, 3), (None, 4)]

In [47]: list(itertools.zip_longest('ABC', range(5), fillvalue='?'))                                                                      
Out[47]: [('A', 0), ('B', 1), ('C', 2), ('?', 3), ('?', 4)]

10.yield from語法

如果生成器函數(shù)需要產(chǎn)出另一個生成器生成的值, 傳統(tǒng)解決方法是使用嵌套for循環(huán).

例如, 自己實現(xiàn)itertools.chain:

In [1]: def chain(*iterable): 
   ...:     for it in iterable: 
   ...:         for i in it: 
   ...:             yield i 
   ...:                                                                                                                                   

In [2]: s = 'ABC'                                                                                                                         

In [3]: t = tuple(range(3))                                                                                                               

In [4]: list(chain(s, t))                                                                                                                 
Out[4]: ['A', 'B', 'C', 0, 1, 2]

如果使用yield from語法, 則是如下形式:

In [1]: def chain(*iterable): 
   ...:     for i in iterable: 
   ...:         yield from i 
   ...:                                                                                                                                   

In [2]: s = 'ABC'                                                                                                                         

In [3]: t = tuple(range(3))                                                                                                               

In [4]: list(chain(s, t))                                                                                                                 
Out[4]: ['A', 'B', 'C', 0, 1, 2]

yield from完全代替了內(nèi)層的for循環(huán).

除了代替for循環(huán)外, yield from還會創(chuàng)建通道, 把內(nèi)層生成器直接與外層生成器的客戶端聯(lián)系起來.

把生成器當(dāng)成協(xié)程使用時, 這個通道特別重要, 不僅能為客戶端代碼產(chǎn)生值, 還能使用客戶端代碼提供的值.

11.可迭代的歸約函數(shù)

歸約函數(shù)(合攏函數(shù)/累加函數(shù)): 接受一個可迭代對象, 返回單個結(jié)果.

其實,這里每個列出的函數(shù)都可以使用itertools.reduce函數(shù)實現(xiàn), 內(nèi)置是因為它們便于解決常見的問題.

此外對于allany函數(shù)來說, 有一項優(yōu)化措施是reduce函數(shù)做不到的: 短路(即一旦確定了結(jié)果就立即停止使用迭代器).

模塊 函數(shù) 說明
內(nèi)置 all(it) it中所有元素為真時返回True, 否則返回False; all([])返回True
內(nèi)置 any(it) 只要it中有元素為真值就返回True, 否則返回False; any([])返回False
內(nèi)置 max(it, [key=, ][default=]) 返回it中最大的元素; key是排序函數(shù), 與sorted函數(shù)中的一樣; 如果可迭代的對象為空, 返回default
內(nèi)置 min(it, [key=, ][default=]) 返回it中最小的元素; 其他同上
functools reduce(func, it, [initial]) 把前兩個元素傳給func, 然后把計算結(jié)果和第三個元素傳給func, 以此類推, 返回最后的結(jié)果; 如果提供了initial, 把他當(dāng)做第一個元素傳入
內(nèi)置 sum(it, start=0) it中所有元素的和. 如果提供可選的start, 會把它加上

12.深入分析iter函數(shù)

鮮為人知的用法: 傳入兩個參數(shù), 使用常規(guī)的函數(shù)或任何可調(diào)用的對象創(chuàng)建迭代器.

  • 第一個參數(shù)必須是可調(diào)用的對象, 用于不斷調(diào)用(沒有參數(shù))產(chǎn)出各個值
  • 第二個參數(shù)是哨符, 是個標(biāo)記值, 當(dāng)可調(diào)用的對象返回這個值時, 觸發(fā)迭代器拋出StopIteration異常, 而不產(chǎn)出哨符.

示例如下:

In [1]: from random import randint                                                                                                        

In [2]: def d6(): 
   ...:     return randint(1, 6) 
   ...:                                                                                                                                   

In [3]: d6_iter = iter(d6, 1)                                                                                                             

In [4]: d6_iter                                                                                                                           
Out[4]: <callable_iterator at 0x108c05c50>

In [5]: for roll in d6_iter: 
   ...:     print(roll) 
   ...:                                                                                                                                   
5

In [6]:  

內(nèi)置函數(shù)iter文檔中給了一個很好的例子: 逐行讀取文件, 直到遇到特定的行為止.

with open('mydata.txt') as fp:
    for line in iter(fp.readline, ''):
        process_line(line)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容