更深入理解 Python 中的迭代
(點選上方公眾號,可快速關注)
編譯: linux中國 / MjSeven 英文: Trey Hunner
https://linux.cn/article-9681-1.html
深入探討 Python 的
for
迴圈來看看它們在底層如何工作,以及為什麼它們會按照它們的方式工作。
Python 的 for
迴圈不會像其他語言中的 for
迴圈那樣工作。在這篇文章中,我們將深入探討 Python 的 for
迴圈來看看它們在底層如何工作,以及為什麼它們會按照它們的方式工作。
迴圈的問題
我們將通過看一些“陷阱”開始我們的旅程,在我們瞭解迴圈如何在 Python 中工作之後,我們將再次看看這些問題並解釋發生了什麼。
問題 1:迴圈兩次
假設我們有一個數字列表和一個生成器,生成器會返回這些數字的平方:
>>> numbers = [1,2,3,5,7]
>>> squares = (n**2forninnumbers)
我們可以將生成器物件傳遞給 tuple
構造器,從而使其變為一個元組:
>>> tuple(squares)
(1,4,9,25,49)
如果我們使用相同的生成器物件並將其傳給 sum
函式,我們可能會期望得到這些數的和,即 88
。
>>> sum(squares)
0
但是我們得到了 0
。
問題 2:包含的檢查
讓我們使用相同的數字列表和相同的生成器物件
>>> numbers = [1,2,3,5,7]
>>> squares = (n**2forninnumbers)
如果我們詢問 9
是否在 squares
生成器中,Python 將會告訴我們 9 在 squares
中。但是如果我們再次詢問相同的問題,Python 會告訴我們 9 不在 squares
中。
>>> 9insquares
True
>>> 9insquares
False
我們詢問相同的問題兩次,Python 給了兩個不同的答案。
問題 3 :拆包
這個字典有兩個鍵值對:
>>> counts = {'apples': 2, 'oranges': 1}
讓我們使用多個變數來對這個字典進行拆包:
>>> x, y = counts
你可能會期望當我們對這個字典進行拆包時,我們會得到鍵值對或者得到一個錯誤。
但是解包字典不會引發錯誤,也不會返回鍵值對。當你解包一個字典時,你會得到鍵:
>>> x
'apples'
回顧:Python 的 for 迴圈
在我們瞭解一些關於這些 Python 片段的邏輯之後,我們將回到這些問題。
Python 沒有傳統的 for
迴圈。為了解釋我的意思,讓我們看一看另一種程式語言的 for
迴圈。
這是一種傳統 C 風格的 for
迴圈,用 JavaScript 編寫:
let numbers = [1,2,3,5,7];
for(leti = 0;i < numbers.length;i += 1){
print(numbers[i])
}
JavaScript、 C、 C++、 Java、 PHP 和一大堆其他程式語言都有這種風格的 for
迴圈,但是 Python 確實沒有。
Python 確實沒有 傳統 C 風格的 for
迴圈。在 Python 中確實有一些我們稱之為 for
迴圈的東西,但是它的工作方式類似於 foreach 迴圈。
這是 Python 的 for
迴圈的風格:
numbers = [1,2,3,5,7]
forninnumbers:
print(n)
與傳統 C 風格的 for
迴圈不同,Python 的 for
迴圈沒有索引變數,沒有索引變數初始化,邊界檢查,或者索引遞增。Python 的 for
迴圈完成了對我們的 numbers
列表進行遍歷的所有工作。
因此,當我們在 Python 中確實有 for
迴圈時,我們沒有傳統 C 風格的 for
迴圈。我們稱之為 for 迴圈的東西的工作機制與之相比有很大的不同。
定義:可迭代和序列
既然我們已經解決了 Python 世界中無索引的 for
迴圈,那麼讓我們在此之外來看一些定義。
可迭代是任何你可以用 Python 中的 for
迴圈遍歷的東西。可迭代意味著可以遍歷,任何可以遍歷的東西都是可迭代的。
foritem insome_iterable:
print(item)
序列是一種非常常見的可迭代型別,列表,元組和字串都是序列。
>>> numbers = [1,2,3,5,7]
>>> coordinates = (4,5,7)
>>> words = "hello there"
序列是可迭代的,它有一些特定的特徵集。它們可以從 0
開始索引,以小於序列的長度結束,它們有一個長度並且它們可以被切分。列表,元組,字串和其他所有序列都是這樣工作的。
>>> 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**2forninmy_set)
因此,任何可以用 for
迴圈遍歷的東西都是可迭代的,序列只是一種可迭代的型別,但是 Python 也有許多其他種類的迭代器。
Python 的 for 迴圈不使用索引
你可能認為,Python 的 for
迴圈在底層使用了索引進行迴圈。在這裡我們使用 while
迴圈和索引手動遍歷:
numbers = [1,2,3,5,7]
i = 0
whilei < len(numbers):
print(numbers[i])
i += 1
這適用於列表,但它不會對所有東西都起作用。這種迴圈方式只適用於序列。
如果我們嘗試用索引去手動遍歷一個集合,我們會得到一個錯誤:
>>> fruits = {'lemon','apple','orange','watermelon'}
>>> i = 0
>>> whilei < len(fruits):
...print(fruits[i])
...i += 1
...
Traceback(most recent call last):
File"<stdin>",line2,in <module>
TypeError: 'set'objectdoes notsupport indexing
集合不是序列,所以它們不支援索引。
我們不能使用索引手動對 Python 中的每一個迭代物件進行遍歷。對於那些不是序列的迭代器來說,這是行不通的。
迭代器驅動 for 迴圈
因此,我們已經看到,Python 的 for
迴圈在底層不使用索引。相反,Python 的 for
迴圈使用迭代器。
迭代器就是可以驅動可迭代物件的東西。你可以從任何可迭代物件中獲得迭代器,你也可以使用迭代器來手動對它的迭代進行遍歷。
讓我們來看看它是如何工作的。
這裡有三個可迭代物件:一個集合,一個元組和一個字串。
>>> numbers = {1,2,3,5,7}
>>> coordinates = (4,5,7)
>>> words = "hello there"
我們可以使用 Python 的內建 iter
函式來訪問這些迭代器,將一個迭代器傳遞給 iter
函式總會給我們返回一個迭代器,無論我們正在使用哪種型別的迭代器。
>>> iter(numbers)
<set_iterator objectat0x7f2b9271c860>
>>> iter(coordinates)
<tuple_iterator objectat0x7f2b9271ce80<