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