新聞中心
深入探討 Python 的 for 循環(huán)來看看它們在底層如何工作,以及為什么它們會按照它們的方式工作。

Python 的 for 循環(huán)不會像其他語言中的 for 循環(huán)那樣工作。在這篇文章中,我們將深入探討 Python 的 for 循環(huán)來看看它們在底層如何工作,以及為什么它們會按照它們的方式工作。
循環(huán)的問題
我們將通過看一些“陷阱”開始我們的旅程,在我們了解循環(huán)如何在 Python 中工作之后,我們將再次看看這些問題并解釋發(fā)生了什么。
問題 1:循環(huán)兩次
假設(shè)我們有一個數(shù)字列表和一個生成器,生成器會返回這些數(shù)字的平方:
>>> numbers = [1, 2, 3, 5, 7]>>> squares = (n**2 for n in numbers)
我們可以將生成器對象傳遞給 tuple 構(gòu)造器,從而使其變?yōu)橐粋€元組:
>>> 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 squaresTrue>>> 9 in squaresFalse
我們詢問相同的問題兩次,Python 給了兩個不同的答案。
問題 3 :拆包
這個字典有兩個鍵值對:
>>> counts = {'apples': 2, 'oranges': 1}
讓我們使用多個變量來對這個字典進(jìn)行拆包:
>>> x, y = counts
你可能會期望當(dāng)我們對這個字典進(jìn)行拆包時,我們會得到鍵值對或者得到一個錯誤。
但是解包字典不會引發(fā)錯誤,也不會返回鍵值對。當(dāng)你解包一個字典時,你會得到鍵:
>>> 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)時,我們沒有傳統(tǒng) C 風(fēng)格的 for 循環(huán)。我們稱之為 for 循環(huán)的東西的工作機(jī)制與之相比有很大的不同。
定義:可迭代和序列
既然我們已經(jīng)解決了 Python 世界中無索引的 for 循環(huán),那么讓我們在此之外來看一些定義。
可迭代是任何你可以用 Python 中的 for 循環(huán)遍歷的東西??傻馕吨梢员闅v,任何可以遍歷的東西都是可迭代的。
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 也有許多其他種類的迭代器。
Python 的 for 循環(huán)不使用索引
你可能認(rèn)為,Python 的 for 循環(huán)在底層使用了索引進(jìn)行循環(huán)。在這里我們使用 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, in TypeError: 'set' object does not support indexing
集合不是序列,所以它們不支持索引。
我們不能使用索引手動對 Python 中的每一個迭代對象進(jìn)行遍歷。對于那些不是序列的迭代器來說,這是行不通的。
迭代器驅(qū)動 for 循環(huán)
因此,我們已經(jīng)看到,Python 的 for 循環(huán)在底層不使用索引。相反,Python 的 for 循環(huán)使用迭代器。
迭代器就是可以驅(qū)動可迭代對象的東西。你可以從任何可迭代對象中獲得迭代器,你也可以使用迭代器來手動對它的迭代進(jìn)行遍歷。
讓我們來看看它是如何工作的。
這里有三個可迭代對象:一個集合,一個元組和一個字符串。
>>> numbers = {1, 2, 3, 5, 7}>>> coordinates = (4, 5, 7)>>> words = "hello there"
我們可以使用 Python 的內(nèi)置 iter 函數(shù)來訪問這些迭代器,將一個迭代器傳遞給 iter 函數(shù)總會給我們返回一個迭代器,無論我們正在使用哪種類型的迭代器。
>>> iter(numbers)>>> iter(coordinates)>>> iter(words)
一旦我們有了迭代器,我們可以做的事情就是通過將它傳遞給內(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)了,你將得到一個 StopIteration 異常:
>>> next(my_iterator)3>>> next(my_iterator)Traceback (most recent call last):File "", line 1, in StopIteration
所以你可以從每個迭代中獲得一個迭代器,迭代器唯一能做的事情就是用 next 函數(shù)請求它們的下一項(xiàng)。如果你將它們傳遞給 next,但它們沒有下一項(xiàng)了,那么就會引發(fā) StopIteration 異常。
你可以將迭代器想象成 Pez 分配器(LCTT 譯注:Pez 是一個結(jié)合玩具的獨(dú)特復(fù)合式糖果),不能重新分配。你可以把 Pez 拿出去,但是一旦 Pez 被移走,它就不能被放回去,一旦分配器空了,它就沒用了。
沒有 for 的循環(huán)
既然我們已經(jīng)了解了迭代器和 iter 以及 next 函數(shù),我們將嘗試在不使用 for 循環(huán)的情況下手動遍歷迭代器。
我們將通過嘗試將這個 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)時得到了一個
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)和迭代器重新定義了 for 循環(huán)。
上面的代碼基本上定義了 Python 在底層循環(huán)的工作方式。如果你理解內(nèi)置的 iter 和 next 函數(shù)的遍歷循環(huán)的工作方式,那么你就會理解 Python 的 for 循環(huán)是如何工作的。
事實(shí)上,你不僅僅會理解 for 循環(huán)在 Python 中是如何工作的,所有形式的遍歷一個可迭代對象都是這樣工作的。
迭代器協(xié)議iterator protocol 是一種很好表示 “在 Python 中遍歷迭代器是如何工作的”的方式。它本質(zhì)上是對 iter 和 next 函數(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 = numbersprint(*numbers)
許多內(nèi)置函數(shù)依賴于迭代器協(xié)議:
unique_numbers = set(numbers)
在 Python 中任何與迭代器一起工作的東西都可能以某種方式使用迭代器協(xié)議。每當(dāng)你在 Python 中遍歷一個可迭代對象時,你將依賴于迭代器協(xié)議。
生成器是迭代器
所以你可能會想:迭代器看起來很酷,但它們看起來像一個實(shí)現(xiàn)細(xì)節(jié),我們作為 Python 的使用者,可能不需要關(guān)心它們。
我有消息告訴你:在 Python 中直接使用迭代器是很常見的。
這里的 squares 對象是一個生成器:
>>> 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)...149
如果你可以在 Python 中循環(huán)遍歷某些東西,那么它就是可迭代的。
所以生成器是迭代器,但是生成器也是可迭代的,這又是怎么回事呢?
我欺騙了你
所以在我之前解釋迭代器如何工作時,我跳過了它們的某些重要的細(xì)節(jié)。
生成器是可迭代的
我再說一遍:Python 中的每一個迭代器都是可迭代的,意味著你可以循環(huán)遍歷迭代器。
因?yàn)榈饕彩强傻?,所以你可以使用?nèi)置 next 函數(shù)從可迭代對象中獲得迭代器:
>>> numbers = [1, 2, 3]>>> iterator1 = iter(numbers)>>> iterator2 = iter(iterator1)
請記住,當(dāng)我們在可迭代對象上調(diào)用 iter 時,它會給我們返回一個迭代器。
當(dāng)我們在迭代器上調(diào)用 iter 時,它會給我們返回它自己:
>>> iterator1 is iterator2True
迭代器是可迭代的,所有的迭代器都是它們自己的迭代器。
def is_iterator(iterable):return iter(iterable) is iterable
迷惑了嗎?
讓我們回顧一些這些措辭。
- 一個可迭代對象是你可以迭代的東西
- 一個迭代對象器是一種實(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)遍歷一次。
正如你在下面的真值表中所看到的,可迭代對象并不總是迭代器,但是迭代器總是可迭代的:
< 如顯示不全,請左右滑動 >
| 對象 | 可迭代? | 迭代器? |
|---|---|---|
| 可迭代對象 | V | ? |
| 迭代器 | V | V |
| 生成器 | V | V |
| 列表 | V | X |
全部的迭代器協(xié)議
讓我們從 Python 的角度來定義迭代器是如何工作的。
可迭代對象可以被傳遞給 iter 函數(shù),以便為它們獲得迭代器。
迭代器:
- 可以傳遞給
next函數(shù),它將給出下一項(xiàng),如果沒有下一項(xiàng),那么它將會引發(fā)StopIteration異常 - 可以傳遞給
iter函數(shù),它會返回一個自身的迭代器
這些語句反過來也是正確的:
- 任何可以在不引發(fā)
TypeError異常的情況下傳遞給iter的東西都是可迭代的 - 任何可以在不引發(fā)
TypeError異常的情況下傳遞給next的東西都是一個迭代器 - 當(dāng)傳遞給
iter時,任何返回自身的東西都是一個迭代器
這就是 Python 中的迭代器協(xié)議。
迭代器的惰性
迭代器允許我們一起工作,創(chuàng)建惰性可迭代對象,即在我們要求它們提供下一項(xiàng)之前,它們不做任何事情。因?yàn)榭梢詣?chuàng)建惰性迭代器,所以我們可以創(chuàng)建無限長的迭代器。我們可以創(chuàng)建對系統(tǒng)資源比較保守的迭代器,可以節(jié)省我們的內(nèi)存,節(jié)省 CPU 時間。
迭代器無處不在
你已經(jīng)在 Python 中看到過許多迭代器,我也提到過生成器是迭代器。Python 的許多內(nèi)置類型也是迭代器。例如,Python 的 enumerate 和 reversed 對象就是迭代器。
>>> letters = ['a', 'b', 'c']>>> e = enumerate(letters)>>> e>>> next(e)(0, 'a')
在 Python 3 中,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 world\n'
在 Python 標(biāo)準(zhǔn)庫和第三方庫中內(nèi)置了大量的迭代器。這些迭代器首先惰性迭代器一樣,延遲工作直到你請求它們下一項(xiàng)。
創(chuàng)建你自己的迭代器
知道你已經(jīng)在使用迭代器是很有用的,但是我希望你也知道,你可以創(chuàng)建自己的迭代器和你自己的惰性迭代器。
下面這個類構(gòu)造了一個迭代器接受一個可迭代的數(shù)字,并在循環(huán)結(jié)束時提供每個數(shù)字的平方。
class square_all:def __init__(self, numbers):self.numbers = iter(numbers)def __next__(self):return next(self.numbers) * 2def __iter__(self):return self
但是在我們開始對該類的實(shí)例進(jìn)行循環(huán)遍歷之前,沒有任何工作要做。
這里,我們有一個無限長的可迭代對象 count,你可以看到 square_all 接受 count 而不用完全循環(huán)遍歷這個無限長的迭代:
>>> from itertools import count>>> numbers = count(5)>>> squares = square_all(numbers)>>> next(squares)25>>> next(squares)36
這個迭代器類是有效的,但我們通常不會這樣做。通常,當(dāng)我們想要做一個定制的迭代器時,我們會生成一個生成器函數(shù):
def square_all(numbers):for n in numbers:yield n**2
這個生成器函數(shù)等價于我們上面所做的類,它的工作原理是一樣的。
這種 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í)是一樣的,但是它使用的語法看起來像是一個列表推導(dǎo)一樣。如果你需要在代碼中使用惰性迭代,請考慮迭代器,并考慮使用生成器函數(shù)或生成器表達(dá)式。
迭代器如何改進(jìn)你的代碼
一旦你已經(jīng)接受了在代碼中使用惰性迭代器的想法,你就會發(fā)現(xiàn)有很多可能來發(fā)現(xiàn)或創(chuàng)建輔助函數(shù),以此來幫助你循環(huán)遍歷和處理數(shù)據(jù)。
惰性求和
這是一個 for 循環(huán),它對 Django queryset 中的所有工作時間求和:
hours_worked = 0for event in events:if event.is_billable():hours_worked += event.duration
下面是使用生成器表達(dá)式進(jìn)行惰性評估的代碼:
billable_times = (event.durationfor event in eventsif event.is_billable())hours_worked = sum(billable_times)
請注意,我們代碼的形狀發(fā)生了巨大變化。
將我們的計(jì)算工作時間變成一個惰性迭代器允許我們能夠命名以前未命名(billable_times)的東西。這也允許我們使用 sum 函數(shù),我們以前不能使用 sum 函數(shù)是因?yàn)槲覀兩踔翛]有一個可迭代對象傳遞給它。迭代器允許你從根本上改變你組織代碼的方式。
惰性和打破循環(huán)
這段代碼打印出日志文件的前 10 行:
for i, line in enumerate(log_file):if i >= 10:breakprint(line)
這段代碼做了同樣的事情,但是我們使用的是 itertools.islice 函數(shù)來惰性地抓取文件中的前 10 行:
from itertools import islicefirst_ten_lines = islice(log_file, 10)for line in first_ten_lines:print(line)
我們定義的 first_ten_lines 變量是迭代器,同樣,使用迭代器允許我們給以前未命名的東西命名(first_ten_lines)。命名事物可以使我們的代碼更具描述性,更具可讀性。
作為獎勵,我們還消除了在循環(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
請注意,這段代碼中有一個額外的變量,我們每次循環(huán)時都要指定它。還要注意,這段代碼只適用于我們可以切片的東西,比如序列。如果 readings 是一個生成器,一個 zip 對象或其他任何類型的迭代器,那么這段代碼就會失敗。
讓我們編寫一個輔助函數(shù)來修復(fù)代碼。
這是一個生成器函數(shù),它為給定的迭代中的每個項(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_itemcurrent = next_item
我們從可迭代對象中手動獲取一個迭代器,在它上面調(diào)用 next 來獲取第一項(xiàng),然后循環(huán)遍歷迭代器獲取后續(xù)所有的項(xiàng)目,跟蹤后一個項(xiàng)目。這個函數(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:耗盡的迭代器
這里我們有一個生成器對象 squares:
>>> numbers = [1, 2, 3, 5, 7]>>> squares = (n**2 for n in numbers)
如果我們把這個生成器傳遞給 tuple 構(gòu)造函數(shù),我們將會得到它的一個元組:
>>> numbers = [1, 2, 3, 5, 7]>>> squares = (n**2 for n in numbers)>>> tuple(squares)(1, 4, 9, 25, 49)
如果我們試著計(jì)算這個生成器中數(shù)字的和,使用 sum,我們就會得到 0:
>>> sum(squares)0
這個生成器現(xiàn)在是空的:我們已經(jīng)把它耗盡了。如果我們試著再次創(chuàng)建一個元組,我們會得到一個空元組:
>>> tuple(squares)()
生成器是迭代器,迭代器是一次性的。它們就像 Hello Kitty Pez 分配器那樣不能重新加載。
問題 2:部分消耗一個迭代器
再次使用那個生成器對象 squares:
>>> numbers = [1, 2, 3, 5, 7]>>> squares = (n**2 for n in numbers)
如果我們詢問 9 是否在 squares 生成器中,我們會得到 True:
>>> 9 in squaresTrue
但是我們再次詢問相同的問題,我們會得到 False:
>>> 9 in squaresFalse
當(dāng)我們詢問 9 是否在迭代器中時,Python 必須對這個生成器進(jìn)行循環(huán)遍歷來找到 9。如果我們在檢查了 9 之后繼續(xù)循環(huán)遍歷,我們只會得到最后兩個數(shù)字,因?yàn)槲覀円呀?jīng)在找到 9 之前消耗了這些數(shù)字:
>>> numbers = [1, 2, 3, 5, 7]>>> squares = (n**2 for n in numbers)>>> 9 in squaresTrue>>> list(squares)[25, 49]
詢問迭代器中是否包含某些東西將會部分地消耗迭代器。如果沒有循環(huán)遍歷迭代器,那么是沒有辦法知道某個東西是否在迭代器中。
問題 3:拆包是迭代
當(dāng)你在字典上循環(huán)時,你會得到鍵:
>>> counts = {'apples': 2, 'oranges': 1}>>> for key in counts:... print(key)...applesoranges
當(dāng)你對一個字典進(jìn)行拆包時,你也會得到鍵:
>>> x, y = counts>>> x, y('apples', 'oranges')
循環(huán)依賴于迭代器協(xié)議,可迭代對象拆包也依賴于有迭代器協(xié)議。拆包一個字典與在字典上循環(huán)遍歷是一樣的,兩者都使用迭代器協(xié)議,所以在這兩種情況下都得到相同的結(jié)果。
回顧
序列是迭代器,但是不是所有的迭代器都是序列。當(dāng)有人說“迭代器”這個詞時,你只能假設(shè)他們的意思是“你可以迭代的東西”。不要假設(shè)迭代器可以被循環(huán)遍歷兩次、詢問它們的長度或者索引。
迭代器是 Python 中最基本的可迭代形式。如果你想在代碼中做一個惰性迭代,請考慮迭代器,并考慮使用生成器函數(shù)或生成器表達(dá)式。
最后,請記住,Python 中的每一種迭代都依賴于迭代器協(xié)議,因此理解迭代器協(xié)議是理解 Python 中的循環(huán)的關(guān)鍵。
這里有一些我推薦的相關(guān)文章和視頻:
- Loop Like a Native, Ned Batchelder 在 PyCon 2013 的講演
- Loop Better ,這篇文章是基于這個講演的
- The Iterator Protocol: How For Loops Work,我寫的關(guān)于迭代器協(xié)議的短文
- Comprehensible Comprehensions,關(guān)于推導(dǎo)和迭代器表達(dá)器的講演
- Python: Range is Not an Iterator,我關(guān)于范圍和迭代器的文章
- Looping Like a Pro in Python,DB 的 PyCon 2017 講演
本文是基于作者去年在 DjangoCon AU、 PyGotham 和 North Bay Python 中發(fā)表的 Loop Better 演講。有關(guān)更多內(nèi)容,請參加將于 2018 年 5 月 9 日至 17 日在 Columbus, Ohio 舉辦的 PYCON。
本文題目:更深入的理解Python中的迭代
網(wǎng)頁URL:http://m.fisionsoft.com.cn/article/cooceep.html


咨詢
建站咨詢
