今天為大家解讀Python中的for循環(huán),深入探討它們在底層是如何開展工作的,以及他們?yōu)槭裁磿凑兆约旱姆绞焦ぷ鳎麄兣c別的語言中的for循環(huán)有什么不同等問題。
循環(huán)的問題
我們將通過看一些“陷阱”開始我們的旅程,在我們了解循環(huán)如何在 Python 中工作之后,我們將再次看看這些問題并解釋發(fā)生了什么。
問題 1:循環(huán)兩次
假設(shè)我們有一個(gè)數(shù)字列表和一個(gè)生成器,生成器會返回這些數(shù)字的平方:
>>> numbers = [1, 2, 3, 5, 7] >>> squares = (n**2 for n in numbers)
我們可以將生成器對象傳遞給 tuple 構(gòu)造器,從而使其變?yōu)橐粋€(gè)元組:
>>> tuple(squares) (1, 4, 9, 25, 49)
如果我們使用相同的生成器對象并將其傳給 sum 函數(shù),我們可能會期望得到這些數(shù)的和,即 88。
>>> sum(squares) 0
但是我們得到了 0。
問題 2:包含的檢查
讓我們使用相同的數(shù)字列表和相同的生成器對象
>>> numbers = [1, 2, 3, 5, 7] >>> squares = (n**2 for n in numbers)
如果我們詢問 9 是否在 squares 生成器中,Python 將會告訴我們 9 在 squares 中。但是如果我們再次詢問相同的問題,Python 會告訴我們 9 不在 squares 中。
>>> 9 in squares True >>> 9 in squares False
我們詢問相同的問題兩次,Python 給了兩個(gè)不同的答案。
問題 3 :拆包
這個(gè)字典有兩個(gè)鍵值對:
>>> counts = {'Apples': 2, 'oranges': 1}
讓我們使用多個(gè)變量來對這個(gè)字典進(jìn)行拆包:
>>> x, y = counts
你可能會期望當(dāng)我們對這個(gè)字典進(jìn)行拆包時(shí),我們會得到鍵值對或者得到一個(gè)錯(cuò)誤。
但是解包字典不會引發(fā)錯(cuò)誤,也不會返回鍵值對。當(dāng)你解包一個(gè)字典時(shí),你會得到鍵:
>>> x 'apples'
回顧:Python 的 for 循環(huán)
在我們了解一些關(guān)于這些 Python 片段的邏輯之后,我們將回到這些問題。
Python 沒有傳統(tǒng)的 for 循環(huán)。為了解釋我的意思,讓我們看一看另一種編程語言的 for 循環(huán)。
這是一種傳統(tǒng) C 風(fēng)格的 for 循環(huán),用 JAVAScript 編寫:
let numbers = [1, 2, 3, 5, 7]; for (let i = 0; i < numbers.length; i += 1) { print(numbers[i]) }
JavaScript、 C、 C++、 Java、 php 和一大堆其他編程語言都有這種風(fēng)格的 for 循環(huán),但是 Python 確實(shí)沒有。
Python 確實(shí)沒有 傳統(tǒng) C 風(fēng)格的 for 循環(huán)。在 Python 中確實(shí)有一些我們稱之為 for 循環(huán)的東西,但是它的工作方式類似于 foreach 循環(huán)。
這是 Python 的 for 循環(huán)的風(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)沒有索引變量,沒有索引變量初始化,邊界檢查,或者索引遞增。Python 的 for 循環(huán)完成了對我們的 numbers 列表進(jìn)行遍歷的所有工作。
因此,當(dāng)我們在 Python 中確實(shí)有 for 循環(huán)時(shí),我們沒有傳統(tǒng) C 風(fēng)格的 for 循環(huán)。我們稱之為 for 循環(huán)的東西的工作機(jī)制與之相比有很大的不同。
定義:可迭代和序列
既然我們已經(jīng)解決了 Python 世界中無索引的 for 循環(huán),那么讓我們在此之外來看一些定義。
可迭代是任何你可以用 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é)束,它們有一個(gè)長度并且它們可以被切分。列表,元組,字符串和其他所有序列都是這樣工作的。
>>> 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 也有許多其他種類的迭代器。
Python 的 for 循環(huán)不使用索引
你可能認(rèn)為,Python 的 for 循環(huán)在底層使用了索引進(jìn)行循環(huán)。在這里我們使用 while 循環(huán)和索引手動遍歷:
numbers = [1, 2, 3, 5, 7] i = 0 while i < len(numbers): print(numbers[i]) i += 1
這適用于列表,但它不會對所有東西都起作用。這種循環(huán)方式只適用于序列。
如果我們嘗試用索引去手動遍歷一個(gè)集合,我們會得到一個(gè)錯(cuò)誤:
>>> fruits = {'lemon', 'apple', 'orange', 'watermelon'} >>> i = 0 >>> while i < len(fruits): ... print(fruits[i]) ... i += 1 ... Traceback (most recent call last): File "<stdin>", line 2, in <module> TypeError: 'set' object does not support indexing
集合不是序列,所以它們不支持索引。
我們不能使用索引手動對 Python 中的每一個(gè)迭代對象進(jìn)行遍歷。對于那些不是序列的迭代器來說,這是行不通的。
迭代器驅(qū)動 for 循環(huán)
因此,我們已經(jīng)看到,Python 的 for 循環(huán)在底層不使用索引。相反,Python 的 for 循環(huán)使用迭代器。
迭代器就是可以驅(qū)動可迭代對象的東西。你可以從任何可迭代對象中獲得迭代器,你也可以使用迭代器來手動對它的迭代進(jìn)行遍歷。
讓我們來看看它是如何工作的。
這里有三個(gè)可迭代對象:一個(gè)集合,一個(gè)元組和一個(gè)字符串。
>>> numbers = {1, 2, 3, 5, 7} >>> coordinates = (4, 5, 7) >>> words = "hello there"
我們可以使用 Python 的內(nèi)置 iter 函數(shù)來訪問這些迭代器,將一個(gè)迭代器傳遞給 iter 函數(shù)總會給我們返回一個(gè)迭代器,無論我們正在使用哪種類型的迭代器。
>>> iter(numbers) <set_iterator object at 0x7f2b9271c860> >>> iter(coordinates) <tuple_iterator object at 0x7f2b9271ce80> >>> iter(words) <str_iterator object at 0x7f2b9271c860>
一旦我們有了迭代器,我們可以做的事情就是通過將它傳遞給內(nèi)置的 next 函數(shù)來獲取它的下一項(xiàng)。
>>> numbers = [1, 2, 3] >>> my_iterator = iter(numbers) >>> next(my_iterator) 1 >>> next(my_iterator) 2
迭代器是有狀態(tài)的,這意味著一旦你從它們中消耗了一項(xiàng),它就消失了。
如果你從迭代器中請求 next 項(xiàng),但是其中沒有更多的項(xiàng)了,你將得到一個(gè) StopIteration異常:
>>> next(my_iterator) 3 >>> next(my_iterator) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
所以你可以從每個(gè)迭代中獲得一個(gè)迭代器,迭代器唯一能做的事情就是用 next 函數(shù)請求它們的下一項(xiàng)。如果你將它們傳遞給 ext,但它們沒有下一項(xiàng)了,那么就會引發(fā) StopIteration 異常。
你可以將迭代器想象成 Pez 分配器(LCTT 譯注:Pez 是一個(gè)結(jié)合玩具的獨(dú)特復(fù)合式糖果),不能重新分配。你可以把 Pez 拿出去,但是一旦 Pez 被移走,它就不能被放回去,一旦分配器空了,它就沒用了。
沒有 for 的循環(huán)
既然我們已經(jīng)了解了迭代器和 iter 以及 next 函數(shù),我們將嘗試在不使用 for 循環(huán)的情況下手動遍歷迭代器。
我們將通過嘗試將這個(gè) for 循環(huán)變?yōu)?while 循環(huán):
def funky_for_loop(iterable, action_to_do): for item in iterable: action_to_do(item)
為了做到這點(diǎn),我們需要:
從給定的可迭代對象中獲得迭代器
反復(fù)從迭代器中獲得下一項(xiàng)
如果我們成功獲得下一項(xiàng),就執(zhí)行 for 循環(huán)的主體
如果我們在獲得下一項(xiàng)時(shí)得到了一個(gè) StopIteration 異常,那么就停止循環(huán)
def funky_for_loop(iterable, action_to_do): iterator = iter(iterable) done_looping = False while not done_looping: try: item = next(iterator) except StopIteration: done_looping = True else: action_to_do(item)
我們只是通過使用 while 循環(huán)和迭代器重新定義了 for 循環(huán)。
上面的代碼基本上定義了 Python 在底層循環(huán)的工作方式。如果你理解內(nèi)置的 iter 和 next函數(shù)的遍歷循環(huán)的工作方式,那么你就會理解 Python 的 for 循環(huán)是如何工作的。
事實(shí)上,你不僅僅會理解 for 循環(huán)在 Python 中是如何工作的,所有形式的遍歷一個(gè)可迭代對象都是這樣工作的。
迭代器協(xié)議iterator protocol 是一種很好表示 “在 Python 中遍歷迭代器是如何工作的”的方式。它本質(zhì)上是對 iter 和 ext 函數(shù)在 Python 中是如何工作的定義。Python 中所有形式的迭代都是由迭代器協(xié)議驅(qū)動的。
迭代器協(xié)議被 for 循環(huán)使用(正如我們已經(jīng)看到的那樣):
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é)議。每當(dāng)你在 Python 中遍歷一個(gè)可迭代對象時(shí),你將依賴于迭代器協(xié)議。
生成器是迭代器
所以你可能會想:迭代器看起來很酷,但它們看起來像一個(gè)實(shí)現(xiàn)細(xì)節(jié),我們作為 Python 的使用者,可能不需要關(guān)心它們。
我有消息告訴你:在 Python 中直接使用迭代器是很常見的。
這里的 squares 對象是一個(gè)生成器:
>>> numbers = [1, 2, 3] >>> squares = (n**2 for n in numbers)
生成器是迭代器,這意味著你可以在生成器上調(diào)用 next 來獲得它的下一項(xiàng):
>>> next(squares) 1 >>> next(squares) 4
但是如果你以前用過生成器,你可能也知道可以循環(huán)遍歷生成器:
>>> squares = (n**2 for n in numbers) >>> for n in squares: ... print(n) ... 1 4 9
如果你可以在 Python 中循環(huán)遍歷某些東西,那么它就是可迭代的。
所以生成器是迭代器,但是生成器也是可迭代的,這又是怎么回事呢?
生成器是可迭代的
我再說一遍:Python 中的每一個(gè)迭代器都是可迭代的,意味著你可以循環(huán)遍歷迭代器。
因?yàn)榈饕彩强傻?,所以你可以使用?nèi)置 next 函數(shù)從可迭代對象中獲得迭代器:
>>> numbers = [1, 2, 3] >>> iterator1 = iter(numbers) >>> iterator2 = iter(iterator1)
請記住,當(dāng)我們在可迭代對象上調(diào)用 iter 時(shí),它會給我們返回一個(gè)迭代器。
當(dāng)我們在迭代器上調(diào)用 iter 時(shí),它會給我們返回它自己:
>>> iterator1 is iterator2 True
迭代器是可迭代的,所有的迭代器都是它們自己的迭代器。
def is_iterator(iterable): return iter(iterable) is iterable
迷惑了嗎?
讓我們回顧一些這些措辭。
一個(gè)可迭代對象是你可以迭代的東西。
一個(gè)迭代對象器是一種實(shí)際上遍歷可迭代對象的代理。
此外,在 Python 中迭代器也是可迭代的,它們充當(dāng)它們自己的迭代器。
所以迭代器是可迭代的,但是它們沒有一些可迭代對象擁有的各種特性。
迭代器沒有長度,它們不能被索引:
>>> 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ù),或者對其進(jìn)行循環(huán)遍歷:
>>> next(iterator) 1 >>> list(iterator) [2, 3, 5, 7]
如果我們第二次循環(huán)遍歷迭代器,我們將一無所獲:
>>> list(iterator) []
你可以把迭代器看作是惰性迭代器,它們是一次性使用,這意味著它們只能循環(huán)遍歷一次。
正如你在下面的真值表中所看到的,可迭代對象并不總是迭代器,但是迭代器總是可迭代的:
全部的迭代器協(xié)議
讓我們從 Python 的角度來定義迭代器是如何工作的。
可迭代對象可以被傳遞給 iter 函數(shù),以便為它們獲得迭代器。
迭代器:
可以傳遞給 next 函數(shù),它將給出下一項(xiàng),如果沒有下一項(xiàng),那么它將會引發(fā) StopIteration 異常。
可以傳遞給 iter 函數(shù),它會返回一個(gè)自身的迭代器。
這些語句反過來也是正確的:
任何可以在不引發(fā) TypeError 異常的情況下傳遞給 iter 的東西都是可迭代的
任何可以在不引發(fā) TypeError 異常的情況下傳遞給 next 的東西都是一個(gè)迭代器
當(dāng)傳遞給 iter 時(shí),任何返回自身的東西都是一個(gè)迭代器
這就是 Python 中的迭代器協(xié)議。
迭代器的惰性
迭代器允許我們一起工作,創(chuàng)建惰性可迭代對象,即在我們要求它們提供下一項(xiàng)之前,它們不做任何事情。因?yàn)榭梢詣?chuàng)建惰性迭代器,所以我們可以創(chuàng)建無限長的迭代器。我們可以創(chuàng)建對系統(tǒng)資源比較保守的迭代器,可以節(jié)省我們的內(nèi)存,節(jié)省 CPU 時(shí)間。
迭代器無處不在
你已經(jīng)在 Python 中看到過許多迭代器,我也提到過生成器是迭代器。Python 的許多內(nèi)置類型也是迭代器。例如,Python 的 enumerate 和 reversed 對象就是迭代器。
>>> letters = ['a', 'b', 'c'] >>> e = enumerate(letters) >>> e <enumerate object at 0x7f112b0e6510> >>> next(e) (0, 'a')
在 Python 3 中,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 中的文件對象也是迭代器。
>>> next(open('hello.txt')) 'hello worldn'
在 Python 標(biāo)準(zhǔn)庫和第三方庫中內(nèi)置了大量的迭代器。這些迭代器首先惰性迭代器一樣,延遲工作直到你請求它們下一項(xiàng)。
創(chuàng)建你自己的迭代器
知道你已經(jīng)在使用迭代器是很有用的,但是我希望你也知道,你可以創(chuàng)建自己的迭代器和你自己的惰性迭代器。
下面這個(gè)類構(gòu)造了一個(gè)迭代器接受一個(gè)可迭代的數(shù)字,并在循環(huán)結(jié)束時(shí)提供每個(gè)數(shù)字的平方。
class square_all: def __init__(self, numbers): self.numbers = iter(numbers) def __next__(self): return next(self.numbers) * 2 def __iter__(self): return self
但是在我們開始對該類的實(shí)例進(jìn)行循環(huán)遍歷之前,沒有任何工作要做。
這里,我們有一個(gè)無限長的可迭代對象 count,你可以看到 square_all 接受 count 而不用完全循環(huán)遍歷這個(gè)無限長的迭代:
>>> from itertools import count >>> numbers = count(5) >>> squares = square_all(numbers) >>> next(squares) 25 >>> next(squares) 36
這個(gè)迭代器類是有效的,但我們通常不會這樣做。通常,當(dāng)我們想要做一個(gè)定制的迭代器時(shí),我們會生成一個(gè)生成器函數(shù):
def square_all(numbers): for n in numbers: yield n**2
這個(gè)生成器函數(shù)等價(jià)于我們上面所做的類,它的工作原理是一樣的。
這種 yield 語句似乎很神奇,但它非常強(qiáng)大:yield 允許我們在調(diào)用 next 函數(shù)之間暫停生成器函數(shù)。yield 語句是將生成器函數(shù)與常規(guī)函數(shù)分離的東西。
另一種實(shí)現(xiàn)相同迭代器的方法是使用生成器表達(dá)式。
def square_all(numbers): return (n**2 for n in numbers)
這和我們的生成器函數(shù)確實(shí)是一樣的,但是它使用的語法看起來像是一個(gè)列表推導(dǎo)一樣。如果你需要在代碼中使用惰性迭代,請考慮迭代器,并考慮使用生成器函數(shù)或生成器表達(dá)式。
迭代器如何改進(jìn)你的代碼
一旦你已經(jīng)接受了在代碼中使用惰性迭代器的想法,你就會發(fā)現(xiàn)有很多可能來發(fā)現(xiàn)或創(chuàng)建輔助函數(shù),以此來幫助你循環(huán)遍歷和處理數(shù)據(jù)。
惰性求和
這是一個(gè) for 循環(huán),它對 Django queryset 中的所有工作時(shí)間求和:
hours_worked = 0 for event in events: if event.is_billable(): hours_worked += event.duration
下面是使用生成器表達(dá)式進(jìn)行惰性評估的代碼:
billable_times = ( event.duration for event in events if event.is_billable() ) hours_worked = sum(billable_times)
請注意,我們代碼的形狀發(fā)生了巨大變化。
將我們的計(jì)算工作時(shí)間變成一個(gè)惰性迭代器允許我們能夠命名以前未命名(billable_times)的東西。這也允許我們使用 sum 函數(shù),我們以前不能使用 sum 函數(shù)是因?yàn)槲覀兩踔翛]有一個(gè)可迭代對象傳遞給它。迭代器允許你從根本上改變你組織代碼的方式。
惰性和打破循環(huán)
這段代碼打印出日志文件的前 10 行:
for i, line in enumerate(log_file): if i >= 10: break print(line)
這段代碼做了同樣的事情,但是我們使用的是 itertools.islice 函數(shù)來惰性地抓取文件中的前 10 行:
from itertools import islice first_ten_lines = islice(log_file, 10) for line in first_ten_lines: print(line)
我們定義的 first_ten_lines 變量是迭代器,同樣,使用迭代器允許我們給以前未命名的東西命名(first_ten_lines)。命名事物可以使我們的代碼更具描述性,更具可讀性。
作為獎(jiǎng)勵(lì),我們還消除了在循環(huán)中使用 break 語句的需要,因?yàn)?islice 實(shí)用函數(shù)為我們處理了中斷。
你可以在標(biāo)準(zhǔn)庫中的 itertools 中找到更多的迭代輔助函數(shù),以及諸如 boltons 和 more-itertools 之類的第三方庫。
創(chuàng)建自己的迭代輔助函數(shù)
你可以在標(biāo)準(zhǔn)庫和第三方庫中找到用于循環(huán)的輔助函數(shù),但你也可以自己創(chuàng)建!
這段代碼列出了序列中連續(xù)值之間的差值列表。
current = readings[0] for next_item in readings[1:]: differences.append(next_item - current) current = next_item
請注意,這段代碼中有一個(gè)額外的變量,我們每次循環(huán)時(shí)都要指定它。還要注意,這段代碼只適用于我們可以切片的東西,比如序列。如果 readings 是一個(gè)生成器,一個(gè) zip 對象或其他任何類型的迭代器,那么這段代碼就會失敗。
讓我們編寫一個(gè)輔助函數(shù)來修復(fù)代碼。
這是一個(gè)生成器函數(shù),它為給定的迭代中的每個(gè)項(xiàng)目提供了當(dāng)前項(xiàng)和下一項(xiàng):
def with_next(iterable): """Yield (current, next_item) tuples for each item in iterable.""" iterator = iter(iterable) current = next(iterator) for next_item in iterator: yield current, next_item current = next_item
我們從可迭代對象中手動獲取一個(gè)迭代器,在它上面調(diào)用 next 來獲取第一項(xiàng),然后循環(huán)遍歷迭代器獲取后續(xù)所有的項(xiàng)目,跟蹤后一個(gè)項(xiàng)目。這個(gè)函數(shù)不僅適用于序列,而且適用于任何類型迭代。
這段代碼和以前代碼是一樣的,但是我們使用的是輔助函數(shù)而不是手動跟蹤 next_item:
differences = [] for current, next_item in with_next(readings): differences.append(next_item - current)
請注意,這段代碼不會掛在我們循環(huán)周圍的 next_item 上,with_next 生成器函數(shù)處理跟蹤 next_item 的工作。
還要注意,這段代碼已足夠緊湊,如果我們愿意,我們甚至可以將方法復(fù)制到列表推導(dǎo)中來。
differences = [ (next_item - current) for current, next_item in with_next(readings) ]
再次回顧循環(huán)問題
現(xiàn)在我們準(zhǔn)備回到之前看到的那些奇怪的例子并試著找出到底發(fā)生了什么。
問題 1:耗盡的迭代器
這里我們有一個(gè)生成器對象 squares:
>>> numbers = [1, 2, 3, 5, 7] >>> squares = (n**2 for n in numbers)
如果我們把這個(gè)生成器傳遞給 tuple 構(gòu)造函數(shù),我們將會得到它的一個(gè)元組:
>>> numbers = [1, 2, 3, 5, 7] >>> squares = (n**2 for n in numbers) >>> tuple(squares) (1, 4, 9, 25, 49)
如果我們試著計(jì)算這個(gè)生成器中數(shù)字的和,使用 sum,我們就會得到 0:
>>> sum(squares) 0
這個(gè)生成器現(xiàn)在是空的:我們已經(jīng)把它耗盡了。如果我們試著再次創(chuàng)建一個(gè)元組,我們會得到一個(gè)空元組:
>>> tuple(squares) ()
生成器是迭代器,迭代器是一次性的。它們就像 Hello Kitty Pez 分配器那樣不能重新加載。
問題 2:部分消耗一個(gè)迭代器
再次使用那個(gè)生成器對象 squares:
>>> numbers = [1, 2, 3, 5, 7] >>> squares = (n**2 for n in numbers)
如果我們詢問 9 是否在 squares 生成器中,我們會得到 True:
>>> 9 in squares True
但是我們再次詢問相同的問題,我們會得到 False:
>>> 9 in squares False
當(dāng)我們詢問 9 是否在迭代器中時(shí),Python 必須對這個(gè)生成器進(jìn)行循環(huán)遍歷來找到 9。如果我們在檢查了 9 之后繼續(xù)循環(huán)遍歷,我們只會得到最后兩個(gè)數(shù)字,因?yàn)槲覀円呀?jīng)在找到 9 之前消耗了這些數(shù)字:
>>> numbers = [1, 2, 3, 5, 7] >>> squares = (n**2 for n in numbers) >>> 9 in squares True >>> list(squares) [25, 49]
詢問迭代器中是否包含某些東西將會部分地消耗迭代器。如果沒有循環(huán)遍歷迭代器,那么是沒有辦法知道某個(gè)東西是否在迭代器中。
問題 3:拆包是迭代
當(dāng)你在字典上循環(huán)時(shí),你會得到鍵:
>>> counts = {'apples': 2, 'oranges': 1} >>> for key in counts: ... print(key) ... apples oranges
當(dāng)你對一個(gè)字典進(jìn)行拆包時(shí),你也會得到鍵:
>>> x, y = counts >>> x, y ('apples', 'oranges')
循環(huán)依賴于迭代器協(xié)議,可迭代對象拆包也依賴于有迭代器協(xié)議。拆包一個(gè)字典與在字典上循環(huán)遍歷是一樣的,兩者都使用迭代器協(xié)議,所以在這兩種情況下都得到相同的結(jié)果。
回顧
序列是迭代器,但是不是所有的迭代器都是序列。當(dāng)有人說“迭代器”這個(gè)詞時(shí),你只能假設(shè)他們的意思是“你可以迭代的東西”。不要假設(shè)迭代器可以被循環(huán)遍歷兩次、詢問它們的長度或者索引。
迭代器是 Python 中最基本的可迭代形式。如果你想在代碼中做一個(gè)惰性迭代,請考慮迭代器,并考慮使用生成器函數(shù)或生成器表達(dá)式。
最后,請記住,Python 中的每一種迭代都依賴于迭代器協(xié)議,因此理解迭代器協(xié)議是理解 Python 中的循環(huán)的關(guān)鍵。