這幾個玩意比較繞礼华,雖然平時用起來一般沒什么問題咐鹤,但看一些文檔的時候,什么iterable, iterator一不留神就傻傻分不清楚了
注: 下文代碼里會使用兩個內(nèi)置模塊
import itertools import collections
基本概念
- iterable: 可迭代對象圣絮,簡單來說就是可以運(yùn)行
for i in obj
而不報錯的對象 - iterator: 迭代器祈惶,是用來對可迭代對象進(jìn)行迭代的一個工具。一般指實(shí)現(xiàn)了迭代器協(xié)議(即
__iter__
和next
方法)的類實(shí)例扮匠,判斷標(biāo)準(zhǔn)是assert isinstance(obj, collections.Iterator)
- generator: 生成器捧请,是一種迭代器,使用yield關(guān)鍵字實(shí)現(xiàn)棒搜。(此處僅作對比疹蛉,不考慮生成器的其他用法)
- sequence: 序列,指實(shí)現(xiàn)了序列協(xié)議的類實(shí)例力麸,內(nèi)置的包括:
str, unicode, list, tuple, bytearray, buffer, xrange
- 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)典的例子是range
和xrange
荐糜,后者的__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筆記
歡迎大家來拍磚~