Python-可迭代對象兑凿,迭代器凯力,生成器,序列

這幾個玩意比較繞礼华,雖然平時用起來一般沒什么問題咐鹤,但看一些文檔的時候,什么iterable, iterator一不留神就傻傻分不清楚了

注: 下文代碼里會使用兩個內(nèi)置模塊

import itertools
import collections

基本概念

  1. iterable: 可迭代對象圣絮,簡單來說就是可以運(yùn)行for i in obj而不報錯的對象
  2. iterator: 迭代器祈惶,是用來對可迭代對象進(jìn)行迭代的一個工具。一般指實(shí)現(xiàn)了迭代器協(xié)議(即__iter__next方法)的類實(shí)例扮匠,判斷標(biāo)準(zhǔn)是assert isinstance(obj, collections.Iterator)
  3. generator: 生成器捧请,是一種迭代器,使用yield關(guān)鍵字實(shí)現(xiàn)棒搜。(此處僅作對比疹蛉,不考慮生成器的其他用法)
  4. sequence: 序列,指實(shí)現(xiàn)了序列協(xié)議的類實(shí)例力麸,內(nèi)置的包括: str, unicode, list, tuple, bytearray, buffer, xrange
  5. iter(obj): 一個內(nèi)置函數(shù)可款,可以返回傳入obj對應(yīng)的迭代器,這里要求obj必須實(shí)現(xiàn)了迭代器協(xié)議或序列協(xié)議末盔。

詳細(xì)解讀

iterable 可迭代對象

首先是一個令人難受的消息筑舅,可迭代對象的判別標(biāo)準(zhǔn),到現(xiàn)在都沒有一個優(yōu)雅的方式陨舱,我們只能說:

可以運(yùn)行for i in obj不報錯的翠拣,或者可以運(yùn)行iter(obj)而不報錯的,就是可迭代對象

參見帖子: https://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable

這里面的邏輯是這樣的: for i in obj里游盲,會調(diào)用iter函數(shù)返回迭代器误墓,然后使用迭代器進(jìn)行迭代蛮粮。iter函數(shù)會根據(jù)兩個協(xié)議得到一個迭代器返回,兩個協(xié)議如下:

第一種谜慌,實(shí)現(xiàn)了__iter__方法的然想,因?yàn)檫@個方法返回的就是迭代器,iter可以直接使用這個迭代器

第二種欣范,實(shí)現(xiàn)了__getitem__变泄,并且是數(shù)字索引,并且是從0開始依次+1的數(shù)字索引的恼琼,iter函數(shù)會構(gòu)造一個迭代器返回妨蛹。代碼大概是這樣,這里寫yield是為了表述方便晴竞,實(shí)際代碼應(yīng)該是用C語言寫的

def foo2(obj):
    i = 0
    while 1:
        try:
            yield obj.__getitem__(i)  # 這句等價于 yield obj[i]
        except IndexError:
            return
        i += 1

因?yàn)檫@里只except了IndexError蛙卤,所以可以避免對只實(shí)現(xiàn)了__getitem__的mapping類對象的誤判,舉個例子

class ST(object):
    def __init__(self):
        self.info = 'qwer'

    def __getitem__(self, item):
        return self.info[item]

class CT(object):
    def __init__(self):
        self.info = {
            0: 'a',
            1: 'b',
            2: 'c',
        }

    def __getitem__(self, item):
        return self.info[item]

print(list(iter(ST())))  # ['q', 'w', 'e', 'r']
print(list(iter(CT())))  # 會報錯 KeyError: 4

為了下面表述方便噩死,我將實(shí)現(xiàn)了第一類協(xié)議的iterable稱呼為一類iterable颤难,實(shí)現(xiàn)了第二類協(xié)議的稱呼為二類iterable

對一類iterable,可以有一種比較好的判斷方式assert isinstance(obj, collections.Iterable)

還有一個需要注意的是已维,可迭代對象不一定是有序的行嗤。比如字符串就一定有序,集合就一定無序衣摩。

iterator 迭代器

參見官網(wǎng)文檔: https://docs.python.org/2/library/stdtypes.html#iterator-types

文檔里指出昂验,只要實(shí)現(xiàn)了迭代器協(xié)議的,就算是迭代器艾扮。也就是只要正確實(shí)現(xiàn)了__iter__next(python3里是__next__)既琴,就是迭代器。這里要求next可以不斷取到下一個泡嘴,而迭代器的__iter__一定返回自身

普通迭代器在使用時甫恩,基本就是瘋狂調(diào)用next,直到拋出StopIteration異常酌予。下面代碼里寫yield同樣只為表述方便磺箕,并不是實(shí)際代碼

def foooo(obj):
    while 1:
        try:
            yield obj.next()
        except StopIteration:
            return

需要注意的很重要的一點(diǎn)是,迭代器是可耗盡的抛虫,也就是一次性的松靡。舉例來說,如下代碼建椰,a是一個迭代器雕欺,第一次循環(huán)的時候可以正常使用,輸出結(jié)果,第二次使用時屠列,因?yàn)榈饕押谋M啦逆,所以什么都輸出不了,這時候如果再調(diào)動一下a.next()笛洛,就必定會報StopIteration

a = [1, 2, 3].__iter__()
print a
print list(i for i in a)
print list(i for i in a)

輸出結(jié)果是: 
<listiterator object at 0x109021610>
[1, 2, 3]
[]

那如果想重復(fù)使用一個迭代器夏志,該怎么做呢,內(nèi)置的itertools模塊有一個tee(iterable, n=2)方法苛让,接收一個可迭代對象沟蔑,然后返回n個它的不同的迭代器,這個函數(shù)有個關(guān)于新舊迭代器的小坑狱杰,感興趣的自己去了解吧溉贿,源碼和官方文檔在這 https://docs.python.org/2.7/library/itertools.html#itertools.tee ,應(yīng)該已經(jīng)講的很清楚了浦旱,想看實(shí)例的話可以點(diǎn) 閱讀原文,看我之前寫的itertools模塊的調(diào)用實(shí)例九杂。搞清楚這個函數(shù)颁湖,迭代器的“耗盡”就肯定沒問題了

a = [1, 2, 3].__iter__()
print a
b, c = itertools.tee(a)
print b, c
print list(i for i in b)
print list(i for i in c)

輸出結(jié)果是: 
<listiterator object at 0x10345e610>
<itertools.tee object at 0x103466098> <itertools.tee object at 0x1034660e0>
[1, 2, 3]
[1, 2, 3]

generator 生成器

參見官網(wǎng): https://docs.python.org/2/library/stdtypes.html#generator-types

生成器也是一種迭代器,那么它和一般迭代器相比例隆,有什么優(yōu)勢甥捺?

我認(rèn)為優(yōu)勢有三點(diǎn)

第一點(diǎn)是,生成器比迭代器多實(shí)現(xiàn)了三個方法镀层,send, throw和close镰禾,提供了更多操作,這里不再展開唱逢。

第二點(diǎn)是吴侦,生成器更方便,生成器有兩種構(gòu)造方式坞古,1是函數(shù)類备韧,用yield關(guān)鍵字構(gòu)建,2是列表推導(dǎo)的時候痪枫,把列表推導(dǎo)最外層的[]換成()织堂,這兩種方式中__iter__next這兩個函數(shù)都不需要顯式實(shí)現(xiàn),而是由生成器自動提供奶陈,可以讓代碼更pythonic易阳,也更易讀

第三點(diǎn)是,生成器更適合做計算吃粒,即含有復(fù)雜邏輯的迭代潦俺。我們知道普通迭代器不停next就完了,而生成器一般要求在next之前需要做各種計算和判斷,然后返回合適的值黑竞。雖然這種操作使用迭代器也能實(shí)現(xiàn)捕发,但跟生成器相比實(shí)在是不方便。舉個最經(jīng)典的計算斐波那契數(shù)列的例子很魂,下面是迭代器實(shí)現(xiàn)和生成器實(shí)現(xiàn)扎酷。

class Fib_1(object):
    def __init__(self):
        self.n1, self.n2 = 0, 1

    def __iter__(self):
        return self

    def next(self):
        self.n1, self.n2 = self.n2, self.n1 + self.n2
        return self.n1

def fib_2():
    a, b = 0, 1
    while 1:
        a, b = b, a + b
        yield a

f1 = Fib_1()
print(f1)
assert f1 is iter(f1)
assert isinstance(f1, collections.Iterable)
assert isinstance(f1, collections.Iterator)
print(list(itertools.islice(f1, 0, 10)))

f2 = fib_2()
print(f2)
assert f2 is iter(f2)
assert isinstance(f2, collections.Iterable)
assert isinstance(f2, collections.Iterator)
print(list(itertools.islice(f2, 0, 10)))

輸出結(jié)果為: 
<__main__.Fib_1 object at 0x108793810>
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
<generator object fib_2 at 0x1087a6d20>
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

其實(shí),生成器一般不跟迭代器做比較遏匆,生成器比較的是類似列表推導(dǎo)一類的法挨,先把全量數(shù)據(jù)生成出來,再依次迭代的情況幅聘,那種情況會消耗大量內(nèi)存空間凡纳,而生成器消耗的空間是一定的,跟size沒關(guān)系帝蒿。最經(jīng)典的例子是rangexrange荐糜,后者的__iter__是用的生成器實(shí)現(xiàn),這樣在迭代時葛超,就不會像前者一樣先把整個list算出來再迭代暴氏,而是迭代一個算一個,在數(shù)據(jù)量超大的時候绣张,后者的優(yōu)勢會極為明顯答渔。

如下面兩行,同樣是輸出0~1億序列中的前三位侥涵,第一行會先把這一億個數(shù)算出來沼撕,存到list里,然后再摘前三個芜飘,速度超慢還浪費(fèi)空間务豺。第二行則是一共就生成了三個,時間空間節(jié)省的不是一點(diǎn)半點(diǎn)燃箭。注意冲呢,生成器不是序列,所以不能用切片功能招狸,我這里用的itertools.islice可以對迭代器進(jìn)行切片敬拓,返回的仍然是迭代器,所以最后要用list包一層裙戏,消耗盡迭代器乘凸。

print(list(range(0, 100000000)[:3]))
print(list(itertools.islice(xrange(0, 100000000), 0, 3)))

輸出結(jié)果為: 
[0, 1, 2]
[0, 1, 2]

sequence 序列

參加官網(wǎng)文檔: https://docs.python.org/2/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer-xrange

序列,是指有順序的累榜,有數(shù)字索引的营勤,且索引是從0到序列長度-1的灵嫌,不是mapping的一種結(jié)構(gòu)。python里就只有六種: str, unicode, list, tuple, bytearray, buffer, xrange葛作,自定義的類如果想通過sequence檢測寿羞,要么繼承collections.Sequence,要么繼承Sequence赂蠢,要么使用Sequence.register(MyClass)绪穆,沒有像迭代器那樣實(shí)現(xiàn)幾個方法就可以成為迭代器的操作。我們常用的其實(shí)也就是字符串虱岂,列表玖院,元組以及xrange。這個xrange本身是sequence第岖,但它的__iter__是返回的生成器难菌,這個需要注意下。

我們平時一般也不需要去判斷一個obj是不是序列蔑滓,如果非要判斷的話郊酒,可以參考這個帖子: https://stackoverflow.com/questions/43566044/what-is-pythons-sequence-protocol

這些概念之間的關(guān)系

左與上的關(guān)系 iterable iterator generator
iterable iterable不是iterator
一類iterable iterable.__iter__()返回其iterator
iterator iterator是一類iterable iterator.__iter__()返回自身
generator generator是一類iterable generator是iterator generator.__iter__()返回自身(還是generator)
sequence sequence是二類iterable sequence不是iterator sequence一般不扯生成器,但xrange.__iter__()是用生成器實(shí)現(xiàn)的键袱,所以說具體情況具體分析吧

文章首發(fā)于微信公眾號:Woko筆記
歡迎大家來拍磚~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猎塞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子杠纵,更是在濱河造成了極大的恐慌,老刑警劉巖钩骇,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件比藻,死亡現(xiàn)場離奇詭異,居然都是意外死亡倘屹,警方通過查閱死者的電腦和手機(jī)银亲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纽匙,“玉大人务蝠,你說我怎么就攤上這事≈虻蓿” “怎么了馏段?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長践瓷。 經(jīng)常有香客問我院喜,道長,這世上最難降的妖魔是什么晕翠? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任喷舀,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘硫麻。我一直安慰自己爸邢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布拿愧。 她就那樣靜靜地躺著杠河,像睡著了一般。 火紅的嫁衣襯著肌膚如雪赶掖。 梳的紋絲不亂的頭發(fā)上感猛,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機(jī)與錄音奢赂,去河邊找鬼陪白。 笑死,一個胖子當(dāng)著我的面吹牛膳灶,可吹牛的內(nèi)容都是我干的咱士。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼轧钓,長吁一口氣:“原來是場噩夢啊……” “哼序厉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起毕箍,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤弛房,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后而柑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體文捶,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年媒咳,在試婚紗的時候發(fā)現(xiàn)自己被綠了粹排。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡涩澡,死狀恐怖顽耳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妙同,我是刑警寧澤射富,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站粥帚,受9級特大地震影響辉浦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茎辐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一宪郊、第九天 我趴在偏房一處隱蔽的房頂上張望掂恕。 院中可真熱鬧,春花似錦弛槐、人聲如沸懊亡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽店枣。三九已至,卻和暖如春叹誉,著一層夾襖步出監(jiān)牢的瞬間鸯两,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工长豁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钧唐,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓匠襟,卻偏偏與公主長得像钝侠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子酸舍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349

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