前言
循環(huán),特別是for循環(huán),是Python/ target=_blank class=infotextkey>Python中常見的語句,甚至于Guido van Rossum(Python創(chuàng)始人)在評論遞歸的時候說過在Python中“遞歸已死”,我想這句話的意思不是說在Python中不能用遞歸,而是說因為Python中的for循環(huán)語句足夠強大,可以不考慮遞歸,而是用for循環(huán)實現(xiàn)原本用遞歸做的事情。
本文就在以上兩本書所述基礎(chǔ)上,從更深入和綜合的角度進(jìn)行闡述,以便能更好地使用for循環(huán)。
在實用for循環(huán)中,特別是初學(xué)者,會遇到很多坑,這里列舉幾個,看看你是否遇到過?
1、第二次無果
假設(shè)有一個數(shù)字組成的列表和一個生成器,生成器給出這些數(shù)字的平方:
>>> numbers = [1, 2, 3, 5, 7]>>> squares = (n**2 for n in numbers)
用tuple函數(shù),將squares轉(zhuǎn)化為元組。
>>> tuple(squares)(1, 4, 9, 25, 49)
現(xiàn)在,又向計算這個生成器對象squares里面所有數(shù)字的和,觀察一下,應(yīng)該能看出來,其和是88,然而:
>>> sum(squares)0
這里計算結(jié)果為0,是Python的BUG嗎?
2、檢查無效
再用下面的方法得到那個生成器對象:
>>> numbers = [1, 2, 3, 5, 7]>>> squares = (n**2 for n in numbers)
如果檢查9是否在squares生成器中,顯然這是真的True。但是同樣的檢查如果再做一遍,就不是這個結(jié)果了——不可重復(fù),不科學(xué)?
>>> 9 in squaresTrue>>> 9 in squaresFalse
3、解包
創(chuàng)建一個包含兩個鍵值對的字典:
>>> counts = {'Apples': 2, 'oranges': 1}
用多變量的賦值語句對字典解包:
>>> x, y = counts
先猜一下,這樣做會有什么結(jié)果?報錯,還是兩個變量分別引用了兩個鍵值對——引用鍵值對,兼職不可能吧,除非將鍵值對包裹在字典里。
但是,一沒有報錯,二沒有返回鍵值對,而是:
'apples'
這似乎也合乎情理和邏輯。
傳統(tǒng)的for循環(huán)
溫故而知新,先來回顧一下for循環(huán)。
嚴(yán)格地說,Python中的for循環(huán)并不“傳統(tǒng)”,或者說不符合眾多語言中所繼承的C語言風(fēng)格的for循環(huán)。
先看一看所謂的C語言風(fēng)格的for循環(huán),以JAVAScript為例:
var numbers = [1, 2, 3, 5, 7];for (var i = 0; i < numbers.length; i += 1) {print(numbers[i]) }
像人們熟知的JavaScript, C, C++, Java, php等很多編程語言的for循環(huán),都是這個樣子的,所以,不少人認(rèn)為這樣的才是真正的for循環(huán)。
但是,Python盲從,而是特立獨行地創(chuàng)造了自己的for循環(huán)。它不是C語言風(fēng)格的,而是Python風(fēng)格的:
numbers = [1, 2, 3, 5, 7]for n in numbers:print(n)
與傳統(tǒng)的C語言風(fēng)格的for循環(huán)不同,Python的for循環(huán)不需要創(chuàng)建索引,不需要對索引變量進(jìn)行初始化,不需要進(jìn)行邊界檢查,也不需要讓索引遞增。Python的for循環(huán)為我們完成了在numbers列表上循環(huán)的所有工作。
因此,Python中雖有for循環(huán),但并非傳統(tǒng)的C風(fēng)格,那么其工作原理亦與之不同。
可迭代對象和序列
在Python中,可迭代對象就是可以用for來循環(huán)的東西。
for item in some_iterable:print(item)
序列是一種非常常見的可迭代對象,例如列表、元組和字符串都是序列。
>>> numbers = [1, 2, 3, 5, 7]>>> coordinates = (4, 5, 7)>>> words = "hello there"
序列是具有一組特定特征的可迭代對象,它們可以從0開始索引,并在比序列長度少一個元素的地方結(jié)束。它們有長度,并且可以切片。列表、元組、字符串和所有其他序列都是這樣工作的。
>>> numbers[0]1>>> coordinates[2]7>>> words[4]'o'
Python中的很多東西都是可迭代對象,但并非所有的可迭代對象都是序列。集合、字典、文件和生成器都是可迭代對象,但它們都不是序列。
>>> my_set = {1, 2, 3}>>> my_dict = {'k1': 'v1', 'k2': 'v2'}>>> my_File = open('some_file.txt')>>> squares = (n**2 for n in my_set)
因此,任何可以用for來循環(huán)的東西都是一個可迭代對象,例如序列,但是并非所有可迭代對象都是序列。
Python的for循環(huán)
前面已經(jīng)顯示了,Python的for循環(huán)不使用索引——這是不同于C語言分割的for循環(huán)之處。
不過,你可能會悄悄滴認(rèn)為,如果非要用,Python的for循環(huán)肯定也能實現(xiàn)C語言風(fēng)格,因為我們一向認(rèn)為“C語言是任何東西的基礎(chǔ)”。為此,我們使用while 循環(huán)和索引手動遍歷一個可迭代對象:
numbers = [1, 2, 3, 5, 7]i = 0while i < len(numbers):print(numbers[i])i += 1
很顯然,上面的循環(huán)方式只適合于序列類對象,對其它的并非完全使用,比如字典、集合。
比如使用索引手動遍歷一個集合,我們將看到報錯:
>>> fruits = {'lemon', 'apple', 'orange', 'watermelon'}>>> i = 0>>> while i < len(fruits):... print(fruits[i])... i += 1... Traceback (most recent call last): File "", line 2, inTypeError: 'set' object does not support indexing
集合不是序列,因此它們不支持索引。
在Python中,我們不能通過使用索引手動遍歷每個可迭代對象。這對于不是序列的可迭代對象根本不起作用。
迭代器
在Python中,迭代器可以用于for循環(huán)。
什么是迭代器?它是驅(qū)動可迭代對象的一類對象。我們可以從任意可迭代對象那里生成迭代器。
這里有三個可迭代對象:集合、元組和字符串。
>>> numbers = {1, 2, 3, 5, 7}>>> coordinates = (4, 5, 7)>>> words = "hello there"
可以用Python內(nèi)置的iter函數(shù)用上面的可迭代對象生成迭代器。
>>> iter(numbers)iterator object at 0x7f2b9271c860>>>> iter(coordinates)>>> iter(words)
有了迭代器,就把它傳給內(nèi)置函數(shù)next,從而獲得它的下一項。
>>> numbers = [1, 2, 3]>>> my_iterator = iter(numbers)>>> next(my_iterator)1>>> next(my_iterator)2
每從迭代器中取出一項,那一項就從迭代器中“消失”了。如果到了迭代器的最后一項,還執(zhí)行next,而實際上后面已經(jīng)沒有其他項了,這時候就會報出StopIteration異常。
>>> next(iterator)3>>> next(iterator)Traceback (most recent call last): File "", line 1,in StopIteration
不用for的循環(huán)
在了解了迭代器、以及iter和next函數(shù)后,我們將嘗試手動遍歷一個可迭代對象,而不使用for循環(huán)。
不用for,就得用while了,Python中只有這么兩個循環(huán)語句。
def funky_for_loop(iterable, action_to_do):for item in iterable:action_to_do(item)
為了去掉for,需要:
- 根據(jù)給定的可迭代對象生成迭代器
- 從迭代器中重復(fù)獲取下一項
- 如果成功獲得了下一項,則相當(dāng)于執(zhí)行for循環(huán)了
- 如果在獲取下一項時遇到“StopIteration”異常,則停止循環(huán)
def funky_for_loop(iterable, action_to_do):iterator = iter(iterable)done_looping = Falsewhile not done_looping:try:item = next(iterator)except StopIteration:done_looping = Trueelse:action_to_do(item)
這里,其實是用while循環(huán)和迭代器重新發(fā)明了for循環(huán)。
上面的代碼基本上定義了Python中循環(huán)的工作方式。如果你了解內(nèi)置的iter和next函數(shù)在遍歷對象時的工作方式,那么你就了解了Python的for循環(huán)是如何工作的,它們的工作過程是類似的。
實際上,通過上面的代碼,不僅僅展示了for循環(huán)的工作原理,所有可迭代對象的循環(huán)都如此。
總結(jié)一下,迭代器協(xié)議是描述“Python中可迭代對象的循環(huán)如何工作的”的一種基本方式,它本質(zhì)上是Python中iter和next函數(shù)所定義的,Python中所有形式的迭代都由迭代器協(xié)議提供支持。
迭代器協(xié)議也被用于for:
for n in numbers:print(n)
多重賦值也使用迭代器協(xié)議:
x, y, z = coordinates
下面這種使用*的表達(dá)式也使用迭代器協(xié)議:
a, b, *rest = numbers print(*numbers)
許多內(nèi)置函數(shù)依賴于迭代器協(xié)議:
unique_numbers = set(numbers)
Python中任何與可迭代對象一起工作的東西都可能以某種方式使用迭代器協(xié)議。在Python中,每當(dāng)你遍歷一個可迭代對象時,都依賴于迭代器協(xié)議。
生成器是迭代器
迭代器看起來很酷,不過,它是不是用途有限呢?或者說作為普通的Python編程者,是不是不需要關(guān)心它呢?
非也。
迭代器很常見。
>>> numbers = [1, 2, 3]>>> squares = (n**2 for n in numbers)
此處得到的squares是一個生成器,生成器也是迭代器,這意味著你可以對生成器調(diào)用next,以獲取其下一項:
>>> next(squares)1>>> next(squares)4
用for循環(huán)同樣可以遍歷生成器:
>>> squares = (n**2 for n in numbers)>>> for n in squares:... print(n)... 1 4 9
下面這句話,貌似廢話,但是重要:迭代器是可迭代對象。
這就意味著,可以將迭代器對象作為iter函數(shù)的參數(shù),生成一個新的迭代器對象。不是嗎?
>>> numbers = [1, 2, 3]>>> iterator1 = iter(numbers)>>> iterator2 = iter(iterator1) # 迭代器對象作為參數(shù)
以上最終得到的iterator2是一個迭代器。不過,要注意,iterator1和iterator2的關(guān)系:
>>> iterator1 is iterator2True
iter函數(shù)的參數(shù)如果是一個迭代器,返回對象仍然是該迭代器對象自身。
結(jié)論:迭代器是可迭代對象,所有迭代器都是自己的迭代器。
def is_iterator(iterable):return iter(iterable) is iterable
困惑了嗎?
繼續(xù)。
迭代器沒有長度,因此無法索引。這個認(rèn)識必須要建立起來。
>>> numbers = [1, 2, 3, 5, 7]>>> iterator = iter(numbers)>>> len(iterator)TypeError: object of type 'list_iterator' has no len()>>> iterator[0]TypeError: 'list_iterator' object is not subscriptable
從Python程序員的角度來看,使用迭代器可以做的唯一有用的事情就是:將迭代器傳給內(nèi)置的next函數(shù)、或遍歷迭代器:
>>> next(iterator)1>>> list(iterator)[2, 3, 5, 7]
如果我們第二次遍歷迭代器,我們將一無所獲:
>>> list(iterator)
這就是說,迭代器可以認(rèn)為是一次性的惰性的可迭代對象,這意味著它們只能遍歷一次。
正如上表中所示,可迭代對象并不總是迭代器,但迭代器總是可迭代對象:
所謂迭代器協(xié)議,即:
- 可以作為next函數(shù)的參數(shù),從而獲得對象的下一項,或者在沒有其他項時引發(fā)StopIteration異常。
- 可以作為iter函數(shù)的參數(shù),并返回自身。
反過來說,也成立:
- 任何可以傳給iter而沒有引發(fā)TypeError的對象都是可迭代對象。
- 任何可以傳給next而沒有引發(fā)TypeError的對象都是迭代器。
- 任何在傳給iter時返回自身的對象都是迭代器。
這是Python中的迭代器協(xié)議。
迭代器無處不在
Python中的迭代器很多,例如:
>>> letters = ['a', 'b', 'c']>>> e = enumerate(letters)>>> e>>> next(e)(0, 'a')
在Python3中,zip、map和filter對象也是迭代器。
>>> numbers = [1, 2, 3, 5, 7]>>> letters = ['a', 'b', 'c']>>> z = zip(numbers, letters)>>> z>>> next(z)(1, 'a')
Python中的文件對象也是迭代器。
>>> next(open('hello.txt'))'hello worldn'
在Python、標(biāo)準(zhǔn)庫和第三方Python庫中還有許多內(nèi)置的迭代器。
至此,已經(jīng)可以給出完美的解釋了。