咱們都知道,在Python中,咱們能夠for循環去遍歷一個列表,元組或者range對象。python
for i in [1,2,3]: print(i) for i in range(0,10): print(i)
那底層的原理是什麼樣的呢?這其中涉及到了幾個概念,「可迭代」,「迭代器」,「生成器」等,大部分人可能聽過這些名詞,可是他們具體的含義以及之間的關係可能沒搞清楚,如下就是它們之間的關係圖,接下來咱們就來分析這個關係圖。ide
若是一個對象是可迭代對象,那麼咱們就能夠用for循環去遍歷它,好比列表、元組、字符串等都是可迭代對象。而咱們用for循環去遍歷它的原理就是,先獲取了它的迭代器,而後使用迭代器的next方法去逐一遍歷。函數
a = [1,2,3] # for至關於下面的代碼 for i in a: print(i) # for循環分解(實際是經過Python底層C語言實現的,此處只是演示) ## 第一步: 獲取迭代器 iterator_a = iter(a) ## 第二步: 經過next逐個遍歷 while True: try: print(next(iterator_a)) except StopIteration: ## 第三步:遇到StopIteration異常,中止 break
注意可迭代對象與迭代器的區別,若是一個對象是可迭代對象,那麼咱們就確定能調用iter()方法獲取它的迭代器,而若是一個對象是迭代器,咱們就能用next()方法去拿下一個元素。 咱們能夠用isinstance判斷一個對象是否是可迭代對象,是否是迭代器。spa
>>> from collections.abc import Iterable >>> from collections.abc import Iterator >>> isinstance([1,2,3],Iterable) True >>> isinstance([1,2,3],Iterator) False >>> i = iter([1,2,3]) >>> isinstance(i, Iterable) True >>> isinstance(i, Iterator) True >>> type(i) <class 'list_iterator'>
列表自己不是迭代器,它是可迭代對象,因此你不能用next()操做列表code
>>> a=[1,2] >>> next(a) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'list' object is not an iterator >>> iter_a = iter(a) >>> next(iter_a) 1 >>> next(iter_a) 2 >>> next(iter_a) # 當沒有剩餘元素時,就拋出StopIteration異常 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
可迭代對象必須實現__iter__()函數,返回迭代器,調用對象自身的__iter__()函數與將iter()做用於對象效果是同樣的,同理對__next__()和next()也同樣。對象
>>> a=[1,2,3] >>> iter_a = a.__iter__() >>> next(iter_a) 1 >>> iter_a.__next__() 2
有趣的是,迭代器也是一個可迭代的對象,因此它自己也須要實現__iter__()函數,可是,一個迭代器的迭代器,是它自己,因此可能也有些多餘了。blog
>>> a=[1,2,3] >>> iter_a = iter(a) >>> iter_b = iter(iter_a) >>> iter_c = iter(iter_b) >>> iter_a is iter_b True >>> iter_a == iter_c True >>> for x in iter_a: ... print(x) ... 1 2 3
咱們常常會用到一些內置的迭代器,例如filter和map,注意range是可迭代對象,但不是迭代器。ip
>>> from collections.abc import Iterator >>> from collections.abc import Iterable >>> a=range(10) >>> isinstance(b, Iterable) Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'b' is not defined >>> isinstance(a, Iterable) True >>> isinstance(a, Iterator) False >>> type(a) <class 'range'> >>> print(a) range(0, 10)
filter函數用於對一個列表進行過濾,傳入一個函數和列表,這個函數返回值是True或者False,將列表的元素逐個傳入這個函數,結果爲True的保留,可使用lambda函數。內存
注:在Python2.x中返回值爲list,在Python3.x中返回迭代器作用域
>>> from collections.abc import Iterator >>> from collections.abc import Iterable >>> a=filter(lambda x : x % 2 == 0, [1,2,3,4,5,6]) >>> isinstance(a, Iterable) True >>> isinstance(a, Iterator) True >>> type(a) <class 'filter'> >>> print(a) <filter object at 0x000001A6100A2948> >>> for i in a: ... print(i) ... 2 4 6
若是咱們要想經過下表訪問,能夠把它轉換成list
>>> a=filter(lambda x : x % 2 == 0, [1,2,3,4,5,6]) >>> a = list(a) >>> a[0] 2
map函數接收一個函數與一個列表,將這個函數做用域列表的每一個元素,生成一個新的序列,返回迭代器。
>>> from collections.abc import Iterator >>> from collections.abc import Iterable >>> a=map(lambda x : x * x, [1,2,3]) >>> isinstance(a, Iterable) True >>> isinstance(a, Iterator) True >>> type(a) <class 'map'> >>> print(i) File "<stdin>", line 1 print(i) ^ IndentationError: unexpected indent >>> print(a) <map object at 0x000001A6100C5108> >>> for i in a: ... print(i) ... 1 4 9
咱們將list作一個簡單的封裝,實現一個可迭代的mylist。
class mylist: def __init__(self, l): self.l = l def __iter__(self): return mylist_iterator(self.l) class mylist_iterator: def __init__(self, l): self.l = l self.current = 0 # 記錄當前迭代到哪一個元素了 def __iter__(self): # 迭代器的迭代器返回本身便可 return self def __next__(self): if self.current < len(self.l): self.current += 1 return self.l[self.current-1] else: raise StopIteration a = mylist([1,2]) for x in a: print(x) i = iter(a) print(next(i)) print(next(i)) print(next(i))
上述代碼並無實現迭代器帶來的好處,由於咱們事先傳入了一個列表進去,假如這個列表很大,會佔內存。假如咱們要實現一個相似range()功能,咱們可使用更有效的方法。
class myrange: def __init__(self, max_num): self.max_num = max_num def __iter__(self): return myrange_iterator(self.max_num) class myrange_iterator: def __init__(self, max_num): self.max_num = max_num self.current = 0 # 記錄當前迭代到哪一個元素了 def __iter__(self): # 迭代器的迭代器返回本身便可 return self def __next__(self): if self.current < self.max_num: self.current += 1 return self.current-1 else: raise StopIteration a = myrange(2) for x in a: print(x) i = iter(a) print(next(i)) print(next(i)) print(next(i))
須要注意的是,咱們的myrange不能隨機訪問,只能一次性順序遍歷,只能前進,不能後退,實際Python的range()能夠隨機訪問。
這個答案是No。for循環大部分狀況都做用於可迭代對象,可是有一個例如,若是對象是能夠經過下標訪問的,也能用於for循環。
一個對象若是不能用下標訪問,那麼就會報下面的錯誤,實際上它對應的是一個__getitem__()內置方法。
>>> a={1,2,3} >>> a[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'set' object is not subscriptable >>> a.__getitem__(1) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'set' object has no attribute '__getitem__'
若是咱們實現了__getitem__(),也能經過for循環遍歷。
from collections.abc import Iterator from collections.abc import Iterable class mylist1: def __init__(self, l): self.l = l def __getitem__(self, i): return self.l[i] print(isinstance(a, Iterable)) print(isinstance(a, Iterator)) a = mylist1([1,2,3]) for x in a: print(x)
結果以下,能夠看到a既不是可迭代對象,也不是迭代器。
False False 1 2 3
for循環會先看對象是否是實現了__iter__(),若是是,就用迭代器的方式,若是沒有的話,就看有沒有__getitem__(),都沒有就報錯,報的錯以下:
>>> for x in 5: ... print(x) ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not iterable
那咱們怎麼知道它先去找__iter__()呢?咱們在前面的代碼里加上幾行,
from collections.abc import Iterator from collections.abc import Iterable class mylist1: def __init__(self, l): self.l = l def __getitem__(self, i): return self.l[i] def __iter__(self): return self print(isinstance(a, Iterable)) print(isinstance(a, Iterator)) a = mylist1([1,2,3]) for x in a: print(x)
結果以下:
True False Traceback (most recent call last): File "<ipython-input-13-8e65b9d361a6>", line 1, in <module> runfile('C:/Users/xlniu/test123/test.py', wdir='C:/Users/xlniu/test123') File "C:\Users\xlniu\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 827, in runfile execfile(filename, namespace) File "C:\Users\xlniu\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 110, in execfile exec(compile(f.read(), filename, 'exec'), namespace) File "C:/Users/xlniu/test123/test.py", line 49, in <module> for x in a: TypeError: iter() returned non-iterator of type 'mylist1'
接下來咱們看關係圖的左邊,生成器,生成器是迭代器,迭代器是可迭代對象,因此生成器確定是可迭代對象了。哪些對象是生成器呢?
生成器的來源主要有兩個,一個是生成器表達式,例如(i for i in 'hello, world'), (i for i in range(0,10) if i % 2 == 0),另外一個是生成器函數,生成器函數不使用return返回數據,而使用yield。
咱們來看一下前面說的filter是否是生成器。
>>> from collections.abc import Iterator >>> from collections.abc import Iterable >>> from collections.abc import Generator >>> a = [1,2,3,4,5,6] >>> b = filter(lambda x : x % 2 == 0, a) >>> print(isinstance(b, Generator)) False
它並非一個生成器。
生成器表達式與列表推斷是差很少的,可是它用"()"括起來,而列表推斷用的中括號,通常的語法就是:
expr(val) for val in xxx if yyy
例如
>>> from collections.abc import Generator >>> a = (i for i in range(0, 10)) >>> next(a) 0 >>> next(a) 1 >>> print(isinstance(a, Generator)) True >>> print(type(a)) <class 'generator'> >>> print(a) <generator object <genexpr> at 0x000001A61011B7C8> #經過genexpr獲得的生成器 >>> b = (i.upper() for i in 'hello, world') >>> c = (x for x in range(0,10) if x % 5 == 0) >>> for x in b: ... print(x) ... H E L L O , W O R L D >>> d = [i for i in range(0,10)] >>> print(type(d)) >>> type(d) <class 'list'> >>> print(d) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
若是咱們把生成器表達式用在其餘的對象上,例如set,list等,它們會自動轉換成相應類型。
>>> set(i for i in range(0,5)) # 至關於set( (i for i in range(0,5)) ) {0, 1, 2, 3, 4} >>> set( (i for i in range(0,5)) ) {0, 1, 2, 3, 4} >>> tuple(i for i in range(0,5)) (0, 1, 2, 3, 4) >>> list(i for i in range(0,5)) [0, 1, 2, 3, 4]
另一種生成器經過生成器函數獲得。
from collections.abc import Iterator from collections.abc import Iterable from collections.abc import Generator def myrange(stop): i = 0 while i < stop: yield i #返回i,下次調用時會從這個地方繼續向下執行 i += 1 e = myrange(5) print(isinstance(e, Iterable)) print(isinstance(e, Iterator)) print(isinstance(e, Generator)) print(type(e)) print(e) print(next(e)) for x in e: print(x)
運行結果以下:
True True True <class 'generator'> <generator object myrange at 0x000001CEC7342C48> 0 1 2 3 4
在函數myrange中,有一個特殊的關鍵詞,yield。這個與return相似,可是return後,下次調用會從頭開始,可是使用了yield,咱們的函數就會返回一個生成器,至關於每次執行,都記住了上次的位置,從上次的位置繼續執行。生成器表達式能夠認爲是一種特殊的生成器函數,相似於lambda表達式和普通函數。可是和生成器同樣,生成器表達式也是返回生成器generator對象,一次只返回一個值。