日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
更深入的理解Python中的迭代

深入探討 Python 的 for 循環(huán)來(lái)看看它們?cè)诘讓尤绾喂ぷ?,以及為什么它們?huì)按照它們的方式工作。

Python 的 for 循環(huán)不會(huì)像其他語(yǔ)言中的 for 循環(huán)那樣工作。在這篇文章中,我們將深入探討 Python 的 for 循環(huán)來(lái)看看它們?cè)诘讓尤绾喂ぷ鳎约盀槭裁此鼈儠?huì)按照它們的方式工作。 

循環(huán)的問(wèn)題

我們將通過(guò)看一些“陷阱”開(kāi)始我們的旅程,在我們了解循環(huán)如何在 Python 中工作之后,我們將再次看看這些問(wèn)題并解釋發(fā)生了什么。 

問(wèn)題 1:循環(huán)兩次

假設(shè)我們有一個(gè)數(shù)字列表和一個(gè)生成器,生成器會(huì)返回這些數(shù)字的平方:

 
 
 
  1. >>> numbers = [1, 2, 3, 5, 7]
  2. >>> squares = (n**2 for n in numbers)

我們可以將生成器對(duì)象傳遞給 tuple 構(gòu)造器,從而使其變?yōu)橐粋€(gè)元組:

 
 
 
  1. >>> tuple(squares)
  2. (1, 4, 9, 25, 49)

如果我們使用相同的生成器對(duì)象并將其傳給 sum 函數(shù),我們可能會(huì)期望得到這些數(shù)的和,即 88。

 
 
 
  1. >>> sum(squares)
  2. 0

但是我們得到了 0。 

問(wèn)題 2:包含的檢查

讓我們使用相同的數(shù)字列表和相同的生成器對(duì)象:

 
 
 
  1. >>> numbers = [1, 2, 3, 5, 7]
  2.  
  3. >>> squares = (n**2 for n in numbers)
  4.  

如果我們?cè)儐?wèn) 9 是否在 squares 生成器中,Python 將會(huì)告訴我們 9 在 squares 中。但是如果我們?cè)俅卧?xún)問(wèn)相同的問(wèn)題,Python 會(huì)告訴我們 9 不在 squares 中。

 
 
 
  1. >>> 9 in squares
  2. True
  3. >>> 9 in squares
  4. False

我們?cè)儐?wèn)相同的問(wèn)題兩次,Python 給了兩個(gè)不同的答案。 

問(wèn)題 3 :拆包

這個(gè)字典有兩個(gè)鍵值對(duì):

 
 
 
  1. >>> counts = {'apples': 2, 'oranges': 1}

讓我們使用多個(gè)變量來(lái)對(duì)這個(gè)字典進(jìn)行拆包:

 
 
 
  1. >>> x, y = counts

你可能會(huì)期望當(dāng)我們對(duì)這個(gè)字典進(jìn)行拆包時(shí),我們會(huì)得到鍵值對(duì)或者得到一個(gè)錯(cuò)誤。

但是解包字典不會(huì)引發(fā)錯(cuò)誤,也不會(huì)返回鍵值對(duì)。當(dāng)你解包一個(gè)字典時(shí),你會(huì)得到鍵:

 
 
 
  1. >>> x
  2. 'apples' 

回顧:Python 的 for 循環(huán)

在我們了解一些關(guān)于這些 Python 片段的邏輯之后,我們將回到這些問(wèn)題。

Python 沒(méi)有傳統(tǒng)的 for 循環(huán)。為了解釋我的意思,讓我們看一看另一種編程語(yǔ)言的 for 循環(huán)。

這是一種傳統(tǒng) C 風(fēng)格的 for 循環(huán),用 JavaScript 編寫(xiě):

 
 
 
  1. let numbers = [1, 2, 3, 5, 7];
  2. for (let i = 0; i < numbers.length; i += 1) {
  3.     print(numbers[i])
  4. }

JavaScript、 C、 C++、 Java、 PHP 和一大堆其他編程語(yǔ)言都有這種風(fēng)格的 for 循環(huán),但是 Python 確實(shí)沒(méi)有

Python 確實(shí)沒(méi)有 傳統(tǒng) C 風(fēng)格的 for 循環(huán)。在 Python 中確實(shí)有一些我們稱(chēng)之為 for 循環(huán)的東西,但是它的工作方式類(lèi)似于 foreach 循環(huán)。

這是 Python 的 for 循環(huán)的風(fēng)格:

 
 
 
  1. numbers = [1, 2, 3, 5, 7]
  2. for n in numbers:
  3.     print(n)

與傳統(tǒng) C 風(fēng)格的 for 循環(huán)不同,Python 的 for 循環(huán)沒(méi)有索引變量,沒(méi)有索引變量初始化,邊界檢查,或者索引遞增。Python 的 for 循環(huán)完成了對(duì)我們的 numbers 列表進(jìn)行遍歷的所有工作。

因此,當(dāng)我們?cè)?Python 中確實(shí)有 for 循環(huán)時(shí),我們沒(méi)有傳統(tǒng) C 風(fēng)格的 for 循環(huán)。我們稱(chēng)之為 for 循環(huán)的東西的工作機(jī)制與之相比有很大的不同。 

定義:可迭代和序列

既然我們已經(jīng)解決了 Python 世界中無(wú)索引的 for 循環(huán),那么讓我們?cè)诖酥鈦?lái)看一些定義。

可迭代是任何你可以用 Python 中的 for 循環(huán)遍歷的東西??傻馕吨梢员闅v,任何可以遍歷的東西都是可迭代的。

 
 
 
  1. for item in some_iterable:
  2.     print(item)

序列是一種非常常見(jiàn)的可迭代類(lèi)型,列表,元組和字符串都是序列。

 
 
 
  1. >>> numbers = [1, 2, 3, 5, 7]
  2. >>> coordinates = (4, 5, 7)
  3. >>> words = "hello there"

序列是可迭代的,它有一些特定的特征集。它們可以從 0 開(kāi)始索引,以小于序列的長(zhǎng)度結(jié)束,它們有一個(gè)長(zhǎng)度并且它們可以被切分。列表,元組,字符串和其他所有序列都是這樣工作的。

 
 
 
  1. >>> numbers[0]
  2. 1
  3. >>> coordinates[2]
  4. 7
  5. >>> words[4]
  6. 'o'

Python 中很多東西都是可迭代的,但不是所有可迭代的東西都是序列。集合、字典、文件和生成器都是可迭代的,但是它們都不是序列。

 
 
 
  1. >>> my_set = {1, 2, 3}
  2. >>> my_dict = {'k1': 'v1', 'k2': 'v2'}
  3. >>> my_file = open('some_file.txt')
  4. >>> squares = (n**2 for n in my_set)

因此,任何可以用 for 循環(huán)遍歷的東西都是可迭代的,序列只是一種可迭代的類(lèi)型,但是 Python 也有許多其他種類(lèi)的迭代器。 

Python 的 for 循環(huán)不使用索引

你可能認(rèn)為,Python 的 for 循環(huán)在底層使用了索引進(jìn)行循環(huán)。在這里我們使用 while 循環(huán)和索引手動(dòng)遍歷:

 
 
 
  1. numbers = [1, 2, 3, 5, 7]
  2. i = 0
  3. while i < len(numbers):
  4.     print(numbers[i])
  5.     i += 1

這適用于列表,但它不會(huì)對(duì)所有東西都起作用。這種循環(huán)方式只適用于序列

如果我們嘗試用索引去手動(dòng)遍歷一個(gè)集合,我們會(huì)得到一個(gè)錯(cuò)誤:

 
 
 
  1. >>> fruits = {'lemon', 'apple', 'orange', 'watermelon'}
  2. >>> i = 0
  3. >>> while i < len(fruits):
  4. ...     print(fruits[i])
  5. ...     i += 1
  6. ...
  7. Traceback (most recent call last):
  8. File "", line 2, in
  9. TypeError: 'set' object does not support indexing

集合不是序列,所以它們不支持索引。

我們不能使用索引手動(dòng)對(duì) Python 中的每一個(gè)迭代對(duì)象進(jìn)行遍歷。對(duì)于那些不是序列的迭代器來(lái)說(shuō),這是行不通的。 

迭代器驅(qū)動(dòng) for 循環(huán)

因此,我們已經(jīng)看到,Python 的 for 循環(huán)在底層不使用索引。相反,Python 的 for 循環(huán)使用迭代器。

迭代器就是可以驅(qū)動(dòng)可迭代對(duì)象的東西。你可以從任何可迭代對(duì)象中獲得迭代器,你也可以使用迭代器來(lái)手動(dòng)對(duì)它的迭代進(jìn)行遍歷。

讓我們來(lái)看看它是如何工作的。

這里有三個(gè)可迭代對(duì)象:一個(gè)集合,一個(gè)元組和一個(gè)字符串。

 
 
 
  1. >>> numbers = {1, 2, 3, 5, 7}
  2. >>> coordinates = (4, 5, 7)
  3. >>> words = "hello there"

我們可以使用 Python 的內(nèi)置 iter 函數(shù)來(lái)訪問(wèn)這些迭代器,將一個(gè)迭代器傳遞給 iter 函數(shù)總會(huì)給我們返回一個(gè)迭代器,無(wú)論我們正在使用哪種類(lèi)型的迭代器。

 
 
 
  1. >>> iter(numbers)
  2. >>> iter(coordinates)
  3. >>> iter(words)

一旦我們有了迭代器,我們可以做的事情就是通過(guò)將它傳遞給內(nèi)置的 next 函數(shù)來(lái)獲取它的下一項(xiàng)。

 
 
 
  1. >>> numbers = [1, 2, 3]
  2. >>> my_iterator = iter(numbers)
  3. >>> next(my_iterator)
  4. 1
  5. >>> next(my_iterator)
  6. 2

迭代器是有狀態(tài)的,這意味著一旦你從它們中消耗了一項(xiàng),它就消失了。

如果你從迭代器中請(qǐng)求 next 項(xiàng),但是其中沒(méi)有更多的項(xiàng)了,你將得到一個(gè) StopIteration 異常:

 
 
 
  1. >>> next(my_iterator)
  2. 3
  3. >>> next(my_iterator)
  4. Traceback (most recent call last):
  5.   File "", line 1, in
  6. StopIteration

所以你可以從每個(gè)迭代中獲得一個(gè)迭代器,迭代器唯一能做的事情就是用 next 函數(shù)請(qǐng)求它們的下一項(xiàng)。如果你將它們傳遞給 next,但它們沒(méi)有下一項(xiàng)了,那么就會(huì)引發(fā) StopIteration 異常。

你可以將迭代器想象成 Pez 分配器(LCTT 譯注:Pez 是一個(gè)結(jié)合玩具的獨(dú)特復(fù)合式糖果),不能重新分配。你可以把 Pez 拿出去,但是一旦 Pez 被移走,它就不能被放回去,一旦分配器空了,它就沒(méi)用了。 

沒(méi)有 for 的循環(huán)

既然我們已經(jīng)了解了迭代器和 iter 以及 next 函數(shù),我們將嘗試在不使用 for 循環(huán)的情況下手動(dòng)遍歷迭代器。

我們將通過(guò)嘗試將這個(gè) for 循環(huán)變?yōu)?while 循環(huán):

 
 
 
  1. def funky_for_loop(iterable, action_to_do):
  2.     for item in iterable:
  3.         action_to_do(item)

為了做到這點(diǎn),我們需要:

  1. 從給定的可迭代對(duì)象中獲得迭代器
  2. 反復(fù)從迭代器中獲得下一項(xiàng)
  3. 如果我們成功獲得下一項(xiàng),就執(zhí)行 for 循環(huán)的主體
  4. 如果我們?cè)讷@得下一項(xiàng)時(shí)得到了一個(gè) StopIteration 異常,那么就停止循環(huán)
 
 
 
  1. def funky_for_loop(iterable, action_to_do):
  2.     iterator = iter(iterable)
  3.     done_looping = False
  4.     while not done_looping:
  5.         try:
  6.             item = next(iterator)
  7.         except StopIteration:
  8.             done_looping = True
  9.         else:
  10.             action_to_do(item)

我們只是通過(guò)使用 while 循環(huán)和迭代器重新定義了 for 循環(huán)。

上面的代碼基本上定義了 Python 在底層循環(huán)的工作方式。如果你理解內(nèi)置的 iternext 函數(shù)的遍歷循環(huán)的工作方式,那么你就會(huì)理解 Python 的 for 循環(huán)是如何工作的。

事實(shí)上,你不僅僅會(huì)理解 for 循環(huán)在 Python 中是如何工作的,所有形式的遍歷一個(gè)可迭代對(duì)象都是這樣工作的。

迭代器協(xié)議iterator protocol 是一種很好表示 “在 Python 中遍歷迭代器是如何工作的”的方式。它本質(zhì)上是對(duì) iternext 函數(shù)在 Python 中是如何工作的定義。Python 中所有形式的迭代都是由迭代器協(xié)議驅(qū)動(dòng)的。

迭代器協(xié)議被 for 循環(huán)使用(正如我們已經(jīng)看到的那樣):

 
 
 
  1. for n in numbers:
  2.     print(n)

多重賦值也使用迭代器協(xié)議:

 
 
 
  1. x, y, z = coordinates

星型表達(dá)式也是用迭代器協(xié)議:

 
 
 
  1. a, b, *rest = numbers
  2. print(*numbers)

許多內(nèi)置函數(shù)依賴(lài)于迭代器協(xié)議:

 
 
 
  1. unique_numbers = set(numbers)

在 Python 中任何與迭代器一起工作的東西都可能以某種方式使用迭代器協(xié)議。每當(dāng)你在 Python 中遍歷一個(gè)可迭代對(duì)象時(shí),你將依賴(lài)于迭代器協(xié)議。 

生成器是迭代器

所以你可能會(huì)想:迭代器看起來(lái)很酷,但它們看起來(lái)像一個(gè)實(shí)現(xiàn)細(xì)節(jié),我們作為 Python 的使用者,可能不需要關(guān)心它們。

我有消息告訴你:在 Python 中直接使用迭代器是很常見(jiàn)的。

這里的 squares 對(duì)象是一個(gè)生成器:

 
 
 
  1. >>> numbers = [1, 2, 3]
  2. >>> squares = (n**2 for n in numbers)

生成器是迭代器,這意味著你可以在生成器上調(diào)用 next 來(lái)獲得它的下一項(xiàng):

 
 
 
  1. >>> next(squares)
  2. 1
  3. >>> next(squares)
  4. 4

但是如果你以前用過(guò)生成器,你可能也知道可以循環(huán)遍歷生成器:

 
 
 
  1. >>> squares = (n**2 for n in numbers)
  2. >>> for n in squares:
  3. ...     print(n)
  4. ...
  5. 1
  6. 4
  7. 9

如果你可以在 Python 中循環(huán)遍歷某些東西,那么它就是可迭代的。

所以生成器是迭代器,但是生成器也是可迭代的,這又是怎么回事呢? 

我欺騙了你

所以在我之前解釋迭代器如何工作時(shí),我跳過(guò)了它們的某些重要的細(xì)節(jié)。 

生成器是可迭代的

我再說(shuō)一遍:Python 中的每一個(gè)迭代器都是可迭代的,意味著你可以循環(huán)遍歷迭代器。

因?yàn)榈饕彩强傻?,所以你可以使用?nèi)置 next 函數(shù)從可迭代對(duì)象中獲得迭代器:

 
 
 
  1. >>> numbers = [1, 2, 3]
  2. >>> iterator1 = iter(numbers)
  3. >>> iterator2 = iter(iterator1)

請(qǐng)記住,當(dāng)我們?cè)诳傻鷮?duì)象上調(diào)用 iter 時(shí),它會(huì)給我們返回一個(gè)迭代器。

當(dāng)我們?cè)诘魃险{(diào)用 iter 時(shí),它會(huì)給我們返回它自己:

 
 
 
  1. >>> iterator1 is iterator2
  2. True

迭代器是可迭代的,所有的迭代器都是它們自己的迭代器。

 
 
 
  1. def is_iterator(iterable):
  2.     return iter(iterable) is iterable

迷惑了嗎?

讓我們回顧一些這些措辭。

  • 一個(gè)可迭代對(duì)象是你可以迭代的東西
  • 一個(gè)迭代對(duì)象器是一種實(shí)際上遍歷可迭代對(duì)象的代理

此外,在 Python 中迭代器也是可迭代的,它們充當(dāng)它們自己的迭代器。

所以迭代器是可迭代的,但是它們沒(méi)有一些可迭代對(duì)象擁有的各種特性。

迭代器沒(méi)有長(zhǎng)度,它們不能被索引:

 
 
 
  1. >>> numbers = [1, 2, 3, 5, 7]
  2. >>> iterator = iter(numbers)
  3. >>> len(iterator)
  4. TypeError: object of type 'list_iterator' has no len()
  5. >>> iterator[0]
  6. TypeError: 'list_iterator' object is not subscriptable

從我們作為 Python 程序員的角度來(lái)看,你可以使用迭代器來(lái)做的唯一有用的事情是將其傳遞給內(nèi)置的 next 函數(shù),或者對(duì)其進(jìn)行循環(huán)遍歷:

 
 
 
  1. >>> next(iterator)
  2. 1
  3. >>> list(iterator)
  4. [2, 3, 5, 7]

如果我們第二次循環(huán)遍歷迭代器,我們將一無(wú)所獲:

 
 
 
  1. >>> list(iterator)
  2. []

你可以把迭代器看作是惰性迭代器,它們是一次性使用,這意味著它們只能循環(huán)遍歷一次。

正如你在下面的真值表中所看到的,可迭代對(duì)象并不總是迭代器,但是迭代器總是可迭代的:

< 如顯示不全,請(qǐng)左右滑動(dòng) >

對(duì)象 可迭代? 迭代器?
可迭代對(duì)象V?
迭代器VV
生成器VV
列表VX

 

全部的迭代器協(xié)議

讓我們從 Python 的角度來(lái)定義迭代器是如何工作的。

可迭代對(duì)象可以被傳遞給 iter 函數(shù),以便為它們獲得迭代器。

迭代器:

  • 可以傳遞給 next 函數(shù),它將給出下一項(xiàng),如果沒(méi)有下一項(xiàng),那么它將會(huì)引發(fā) StopIteration 異常
  • 可以傳遞給 iter 函數(shù),它會(huì)返回一個(gè)自身的迭代器

這些語(yǔ)句反過(guò)來(lái)也是正確的:

  • 任何可以在不引發(fā) TypeError 異常的情況下傳遞給 iter 的東西都是可迭代的
  • 任何可以在不引發(fā) TypeError 異常的情況下傳遞給 next 的東西都是一個(gè)迭代器
  • 當(dāng)傳遞給 iter 時(shí),任何返回自身的東西都是一個(gè)迭代器

這就是 Python 中的迭代器協(xié)議。 

迭代器的惰性

迭代器允許我們一起工作,創(chuàng)建惰性可迭代對(duì)象,即在我們要求它們提供下一項(xiàng)之前,它們不做任何事情。因?yàn)榭梢詣?chuàng)建惰性迭代器,所以我們可以創(chuàng)建無(wú)限長(zhǎng)的迭代器。我們可以創(chuàng)建對(duì)系統(tǒng)資源比較保守的迭代器,可以節(jié)省我們的內(nèi)存,節(jié)省 CPU 時(shí)間。 

迭代器無(wú)處不在

你已經(jīng)在 Python 中看到過(guò)許多迭代器,我也提到過(guò)生成器是迭代器。Python 的許多內(nèi)置類(lèi)型也是迭代器。例如,Python 的 enumeratereversed 對(duì)象就是迭代器。

 
 
 
  1. >>> letters = ['a', 'b', 'c']
  2. >>> e = enumerate(letters)
  3. >>> e
  4. >>> next(e)
  5. (0, 'a')

在 Python 3 中,zip, mapfilter 也是迭代器。

 
 
 
  1. >>> numbers = [1, 2, 3, 5, 7]
  2. >>> letters = ['a', 'b', 'c']
  3. >>> z = zip(numbers, letters)
  4. >>> z
  5. >>> next(z)
  6. (1, 'a')

Python 中的文件對(duì)象也是迭代器。

 
 
 
  1. >>> next(open('hello.txt'))
  2. 'hello world\n'

在 Python 標(biāo)準(zhǔn)庫(kù)和第三方庫(kù)中內(nèi)置了大量的迭代器。這些迭代器首先惰性迭代器一樣,延遲工作直到你請(qǐng)求它們下一項(xiàng)。 

創(chuàng)建你自己的迭代器

知道你已經(jīng)在使用迭代器是很有用的,但是我希望你也知道,你可以創(chuàng)建自己的迭代器和你自己的惰性迭代器。

下面這個(gè)類(lèi)構(gòu)造了一個(gè)迭代器接受一個(gè)可迭代的數(shù)字,并在循環(huán)結(jié)束時(shí)提供每個(gè)數(shù)字的平方。

 
 
 
  1. class square_all:
  2.     def __init__(self, numbers):
  3.         self.numbers = iter(numbers)
  4.     def __next__(self):
  5.         return next(self.numbers) * 2
  6.     def __iter__(self):
  7.         return self

但是在我們開(kāi)始對(duì)該類(lèi)的實(shí)例進(jìn)行循環(huán)遍歷之前,沒(méi)有任何工作要做。

這里,我們有一個(gè)無(wú)限長(zhǎng)的可迭代對(duì)象 count,你可以看到 square_all 接受 count 而不用完全循環(huán)遍歷這個(gè)無(wú)限長(zhǎng)的迭代:

 
 
 
  1. >>> from itertools import count
  2. >>> numbers = count(5)
  3. >>> squares = square_all(numbers)
  4. >>> next(squares)
  5. 25
  6. >>> next(squares)
  7. 36

這個(gè)迭代器類(lèi)是有效的,但我們通常不會(huì)這樣做。通常,當(dāng)我們想要做一個(gè)定制的迭代器時(shí),我們會(huì)生成一個(gè)生成器函數(shù):

 
 
 
  1. def square_all(numbers):
  2. for n in numbers:
  3. yield n**2

這個(gè)生成器函數(shù)等價(jià)于我們上面所做的類(lèi),它的工作原理是一樣的。

這種 yield 語(yǔ)句似乎很神奇,但它非常強(qiáng)大:yield 允許我們?cè)谡{(diào)用 next 函數(shù)之間暫停生成器函數(shù)。yield 語(yǔ)句是將生成器函數(shù)與常規(guī)函數(shù)分離的東西。

另一種實(shí)現(xiàn)相同迭代器的方法是使用生成器表達(dá)式。

 
 
 
  1. def square_all(numbers):
  2. return (n**2 for n in numbers)

這和我們的生成器函數(shù)確實(shí)是一樣的,但是它使用的語(yǔ)法看起來(lái)像是一個(gè)列表推導(dǎo)一樣。如果你需要在代碼中使用惰性迭代,請(qǐng)考慮迭代器,并考慮使用生成器函數(shù)或生成器表達(dá)式。 

迭代器如何改進(jìn)你的代碼

一旦你已經(jīng)接受了在代碼中使用惰性迭代器的想法,你就會(huì)發(fā)現(xiàn)有很多可能來(lái)發(fā)現(xiàn)或創(chuàng)建輔助函數(shù),以此來(lái)幫助你循環(huán)遍歷和處理數(shù)據(jù)。 

惰性求和

這是一個(gè) for 循環(huán),它對(duì) Django queryset 中的所有工作時(shí)間求和:

 
 
 
  1. hours_worked = 0
  2. for event in events:
  3. if event.is_billable():
  4. hours_worked += event.duration

下面是使用生成器表達(dá)式進(jìn)行惰性評(píng)估的代碼:

 
 
 
  1. billable_times = (
  2. event.duration
  3. for event in events
  4. if event.is_billable()
  5. )
  6.  
  7. hours_worked = sum(billable_times)

請(qǐng)注意,我們代碼的形狀發(fā)生了巨大變化。

將我們的計(jì)算工作時(shí)間變成一個(gè)惰性迭代器允許我們能夠命名以前未命名(billable_times)的東西。這也允許我們使用 sum 函數(shù),我們以前不能使用 sum 函數(shù)是因?yàn)槲覀兩踔翛](méi)有一個(gè)可迭代對(duì)象傳遞給它。迭代器允許你從根本上改變你組織代碼的方式。 

惰性和打破循環(huán)

這段代碼打印出日志文件的前 10 行:

 
 
 
  1. for i, line in enumerate(log_file):
  2. if i >= 10:
  3. break
  4. print(line)

這段代碼做了同樣的事情,但是我們使用的是 itertools.islice 函數(shù)來(lái)惰性地抓取文件中的前 10 行:

 
 
 
  1. from itertools import islice
  2. first_ten_lines = islice(log_file, 10)
  3. for line in first_ten_lines:
  4. print(line)

我們定義的 first_ten_lines 變量是迭代器,同樣,使用迭代器允許我們給以前未命名的東西命名(first_ten_lines)。命名事物可以使我們的代碼更具描述性,更具可讀性。

作為獎(jiǎng)勵(lì),我們還消除了在循環(huán)中使用 break 語(yǔ)句的需要,因?yàn)?islice 實(shí)用函數(shù)為我們處理了中斷。

你可以在標(biāo)準(zhǔn)庫(kù)中的 itertools 中找到更多的迭代輔助函數(shù),以及諸如 boltons 和 more-itertools 之類(lèi)的第三方庫(kù)。 

創(chuàng)建自己的迭代輔助函數(shù)

你可以在標(biāo)準(zhǔn)庫(kù)和第三方庫(kù)中找到用于循環(huán)的輔助函數(shù),但你也可以自己創(chuàng)建!

這段代碼列出了序列中連續(xù)值之間的差值列表。

 
 
 
  1. current = readings[0]
  2. for next_item in readings[1:]:
  3. differences.append(next_item - current)
  4. current = next_item

請(qǐng)注意,這段代碼中有一個(gè)額外的變量,我們每次循環(huán)時(shí)都要指定它。還要注意,這段代碼只適用于我們可以切片的東西,比如序列。如果 readings 是一個(gè)生成器,一個(gè) zip 對(duì)象或其他任何類(lèi)型的迭代器,那么這段代碼就會(huì)失敗。

讓我們編寫(xiě)一個(gè)輔助函數(shù)來(lái)修復(fù)代碼。

這是一個(gè)生成器函數(shù),它為給定的迭代中的每個(gè)項(xiàng)目提供了當(dāng)前項(xiàng)和下一項(xiàng):

 
 
 
  1. def with_next(iterable):
  2. """Yield (current, next_item) tuples for each item in iterable."""
  3. iterator = iter(iterable)
  4. current = next(iterator)
  5. for next_item in iterator:
  6. yield current, next_item
  7. current = next_item

我們從可迭代對(duì)象中手動(dòng)獲取一個(gè)迭代器,在它上面調(diào)用 next 來(lái)獲取第一項(xiàng),然后循環(huán)遍歷迭代器獲取后續(xù)所有的項(xiàng)目,跟蹤后一個(gè)項(xiàng)目。這個(gè)函數(shù)不僅適用于序列,而且適用于任何類(lèi)型迭代。

這段代碼和以前代碼是一樣的,但是我們使用的是輔助函數(shù)而不是手動(dòng)跟蹤 next_item

 
 
 
  1. differences = []
  2. for current, next_item in with_next(readings):
  3. differences.append(next_item - current)

請(qǐng)注意,這段代碼不會(huì)掛在我們循環(huán)周?chē)?next_item 上,with_next 生成器函數(shù)處理跟蹤 next_item 的工作。

還要注意,這段代碼已足夠緊湊,如果我們?cè)敢?,我們甚至可以將方法?fù)制到列表推導(dǎo)中來(lái)。

 
 
 
  1. differences = [
  2. (next_item - current)
  3. for current, next_item in with_next(readings)
  4. ] 

再次回顧循環(huán)問(wèn)題

現(xiàn)在我們準(zhǔn)備回到之前看到的那些奇怪的例子并試著找出到底發(fā)生了什么。 

問(wèn)題 1:耗盡的迭代器

這里我們有一個(gè)生成器對(duì)象 squares

 
 
 
  1. >>> numbers = [1, 2, 3, 5, 7]
  2. >>> squares = (n**2 for n in numbers)

如果我們把這個(gè)生成器傳遞給 tuple 構(gòu)造函數(shù),我們將會(huì)得到它的一個(gè)元組:

 
 
 
  1. >>> numbers = [1, 2, 3, 5, 7]
  2. >>> squares = (n**2 for n in numbers)
  3. >>> tuple(squares)
  4. (1, 4, 9, 25, 49)

如果我們?cè)囍?jì)算這個(gè)生成器中數(shù)字的和,使用 sum,我們就會(huì)得到 0

 
 
 
  1. >>> sum(squares)
  2. 0

這個(gè)生成器現(xiàn)在是空的:我們已經(jīng)把它耗盡了。如果我們?cè)囍俅蝿?chuàng)建一個(gè)元組,我們會(huì)得到一個(gè)空元組:

 
 
 
  1. >>> tuple(squares)
  2. ()

生成器是迭代器,迭代器是一次性的。它們就像 Hello Kitty Pez 分配器那樣不能重新加載。 

問(wèn)題 2:部分消耗一個(gè)迭代器

再次使用那個(gè)生成器對(duì)象 squares

 
 
 
  1. >>> numbers = [1, 2, 3, 5, 7]
  2. >>> squares = (n**2 for n in numbers)

如果我們?cè)儐?wèn) 9 是否在 squares 生成器中,我們會(huì)得到 True

 
 
 
  1. >>> 9 in squares
  2. True

但是我們?cè)俅卧?xún)問(wèn)相同的問(wèn)題,我們會(huì)得到 False

 
 
 
  1. >>> 9 in squares
  2. False

當(dāng)我們?cè)儐?wèn) 9 是否在迭代器中時(shí),Python 必須對(duì)這個(gè)生成器進(jìn)行循環(huán)遍歷來(lái)找到 9。如果我們?cè)跈z查了 9 之后繼續(xù)循環(huán)遍歷,我們只會(huì)得到最后兩個(gè)數(shù)字,因?yàn)槲覀円呀?jīng)在找到 9 之前消耗了這些數(shù)字:

 
 
 
  1. >>> numbers = [1, 2, 3, 5, 7]
  2. >>> squares = (n**2 for n in numbers)
  3. >>> 9 in squares
  4. True
  5. >>> list(squares)
  6. [25, 49]

詢(xún)問(wèn)迭代器中是否包含某些東西將會(huì)部分地消耗迭代器。如果沒(méi)有循環(huán)遍歷迭代器,那么是沒(méi)有辦法知道某個(gè)東西是否在迭代器中。 

問(wèn)題 3:拆包是迭代

當(dāng)你在字典上循環(huán)時(shí),你會(huì)得到鍵:

 
 
 
  1. >>> counts = {'apples': 2, 'oranges': 1}
  2. >>> for key in counts:
  3. ... print(key)
  4. ...
  5. apples
  6. oranges

當(dāng)你對(duì)一個(gè)字典進(jìn)行拆包時(shí),你也會(huì)得到鍵:

 
 
 
  1. >>> x, y = counts
  2. >>> x, y
  3. ('apples', 'oranges')

循環(huán)依賴(lài)于迭代器協(xié)議,可迭代對(duì)象拆包也依賴(lài)于有迭代器協(xié)議。拆包一個(gè)字典與在字典上循環(huán)遍歷是一樣的,兩者都使用迭代器協(xié)議,所以在這兩種情況下都得到相同的結(jié)果。 

回顧

序列是迭代器,但是不是所有的迭代器都是序列。當(dāng)有人說(shuō)“迭代器”這個(gè)詞時(shí),你只能假設(shè)他們的意思是“你可以迭代的東西”。不要假設(shè)迭代器可以被循環(huán)遍歷兩次、詢(xún)問(wèn)它們的長(zhǎng)度或者索引。

迭代器是 Python 中最基本的可迭代形式。如果你想在代碼中做一個(gè)惰性迭代,請(qǐng)考慮迭代器,并考慮使用生成器函數(shù)或生成器表達(dá)式。

最后,請(qǐng)記住,Python 中的每一種迭代都依賴(lài)于迭代器協(xié)議,因此理解迭代器協(xié)議是理解 Python 中的循環(huán)的關(guān)鍵。

這里有一些我推薦的相關(guān)文章和視頻:

  • Loop Like a Native, Ned Batchelder 在 PyCon 2013 的講演
  • Loop Better ,這篇文章是基于這個(gè)講演的
  • The Iterator Protocol: How For Loops Work,我寫(xiě)的關(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)容,請(qǐng)參加將于 2018 年 5 月 9 日至 17 日在 Columbus, Ohio 舉辦的 PYCON。


文章名稱(chēng):更深入的理解Python中的迭代
分享鏈接:http://m.5511xx.com/article/cooceep.html