1. 程式人生 > >Python 迴圈中的陷阱

Python 迴圈中的陷阱

Python 中的 for 迴圈和其他語言中的 for 迴圈工作方式是不一樣的,今天就帶你深入瞭解 Python 的 for 迴圈,看看它是如何工作的,以及它為什麼按照這種方式工作。

迴圈中的陷阱

我們先來看一下 Python 迴圈中的「陷阱」,在我們瞭解了迴圈的工作方式後,再來看下這些陷阱到底是怎麼出現的。

陷阱 1:迴圈兩次

現在我們先假設有一個數字組成的列表,和一個用於返回這些數字的平方的生成器:

>>> nums = [1, 2, 3, 4]
>>> squares = (n**2 for n in nums)

我們可以將這個生成器物件傳遞給元組構造器,從而可以得到一個元組:

>>> tuple(squares)
(1, 4, 9, 16)

這個時候,如果我們再將這個構造器物件傳遞給 sum 函式,按理說應該會返回這些數字的和吧:

>>> sum(squares)
0

返回的是個 0,先拖住下巴。

陷阱 2:檢查是否包含

我們還是使用上面的數字列表和生成器:

>>> nums = [1, 2, 3, 4]
>>> squares = (n**2 for n in nums)

如果我 squares 生成器中是否包含 9,答案是肯定的,若果我再問一次呢?

你敢答應嗎

>>> 
9 in squares True >>> 9 in squares False

發現,第二次不靈了~

怎麼不靈了

陷阱 3:拆包

現在假設有一個字典:

>>> counts = {1:'a', 2:'b'}

然後,我們用多個變數對字典進行拆包:

>>> x,y = counts

你覺得這時候,x 和 y 中會是什麼?

>>> x
1
>>> y
2

我們只得到了鍵。

下面,我們先來了解下 Python 中的迴圈工作原理,然後再反過頭來看這些陷阱問題。

一些概念

首先,先了解一些基本概念:

可迭代和序列

可迭代就是指任意可以使用 for 迴圈遍歷的東西,可迭代意味著可以遍歷,任何可以遍歷的東西都是可迭代的。

for item in some_iterable:
    print(item)

序列是一種常見的可迭代型別,如列表、元組、字串等。

序列是可迭代的,它有著一些特點,它們是從 0 開始索引,索引長度不超過序列的長度;它們有序列長度;並且它們可以被切分。

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 迴圈遍歷的東西都是可迭代的,序列可迭代的型別中的一種,Python 還有著許多其他種類的可迭代型別。

迭代器

迭代器就是可以驅動可迭代物件的東西。你可以從任何可迭代物件中獲得迭代器,你也可以使用迭代器來手動對它的迭代進行遍歷。

下面有三個可迭代物件:一個集合、一個元祖和一個字串:

>>> nums = {1,2,3,4}
>>> coors = (4,5,6)
>>> words = "hello hoxis"

我們可以使用 Python 的內建函式 iter ,從這些可迭代物件中獲取到迭代器:

>>> iter(nums)
<setiterator object at 0x7fa8c194ad70>
>>> iter(coors)
<tupleiterator object at 0x7fa8c1959610>
>>> iter(words)
<iterator object at 0x7fa8c19595d0>

一旦我們有了迭代器,我們就可以使用其內建函式 next() 來獲取它的下一個值:

>>> nums = {1,2,3,4}
>>> num_iter = iter(nums)
>>> next(num_iter)
1
>>> next(num_iter)
2
>>> next(num_iter)
3
>>> next(num_iter)
4
>>> next(num_iter)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

若果迭代到頭了,也就是沒有下一個值了,就會丟擲 StopIteration 異常。也就是說,它不會繼續迴圈取獲取第一個值。

是不是有點懵逼了?

  • 可迭代物件是可以迭代的東西
  • 迭代物件器實際上是遍歷可迭代物件的代理
  • 迭代器沒有長度,它們不能被索引。
  • 可以使用迭代器來做的唯一有用的事情是將其傳遞給內建的 next 函式,或者對其進行迴圈遍歷
  • 可以使用 list() 函式將迭代器轉換為列表
>>> nums = {1,2,3,4}
>>> num_iter = iter(nums)
>>> next(num_iter)
1
>>> list(num_iter)
[2, 3, 4]
>>> list(num_iter)
[]

若果想再次將其轉換為列表,明顯地,得到的是一個空列表。

其實這也是迭代器的一個重要特性:惰性,只能使用一次,只能迴圈遍歷一次。並且,在我們呼叫 next() 函式之前,它不會做任何事情。因此,我們可以建立無限長的迭代器,而建立無限長的列表則不行,那樣會耗盡你的記憶體!

可迭代物件不一定是迭代器,但是迭代器一定是可迭代的:

物件 可迭代? 迭代器?
可迭代物件 不一定
迭代器
生成器
列表 ×

其實,Python 中有許多迭代器,生成器是迭代器,Python 的許多內建型別也是迭代器。例如,Python 的 enumeratereversed 物件就是迭代器。zip, mapfilter 也是迭代器;檔案物件也是迭代器。

Python 中的 for 迴圈

其實,Python 並沒有傳統的 for 迴圈,什麼是傳統的 for 迴圈?

我們看下 Java 中的 for 迴圈:

int[] integers = {1234};
for (int j = 0; j<integers.length; j++) {
    int i = integers[j];
    System.out.println(i);
}

這是一種 C風格 的 for 迴圈,JavaScript、C、C++、Java、PHP 和一大堆其他程式語言都有這種風格的 for 迴圈,但是 Python 確實沒有。

Python 中的我們稱之為 for 迴圈的東西,確切的說應該是 foreach 迴圈:

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

C風格 的 for 迴圈不同之處在於,Python 的 for 迴圈沒有索引變數,沒有索引變數的初始化,邊界檢查和索引變數的增長。

這就是 Python 的 for 迴圈的不同之處!

使用索引?

你可能會懷疑,Python 的 for 迴圈是否在底層使用了索引,下面我們手動的使用 while 迴圈和索引來遍歷:

>>> nums = [1,2,3,4]
>>> i = 0
>>> while i < len(nums):
...     print(num[i])
...     i += 1
...
0
1
2
3

對於列表,這樣遍歷是可以的,但不代表適用於所有可迭代物件,它只適用於序列

比如,我們對一個 set 使用這種方法遍歷,會得到一個異常:

>>> set = {1,2,3}
>>> i = 0
>>> while i < len(set):
...     print(set[i])
...     i += 1
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: 'set' object does not support indexing

因為 set 不是序列,因此不支援索引遍歷。

我們不能使用索引手動對 Python 中的每一個迭代物件進行遍歷。對於那些不是序列的迭代器來說,更是行不通的。

實現沒有 for 的迴圈

從上文可以看出,Python 中的 for 迴圈不使用索引,它使用的是迭代器。讓我們來看下它是如何工作的。

通過上文,我們瞭解到了迭代器和 iter、next 函式,現在我們可以嘗試不用 for 迴圈來遍歷一個可迭代物件。

下面是一個正常的 for 迴圈:

def funky_for_loop(iterable, action_to_do):
    for item in iterable:
        action_to_do(item)

我們要嘗試用迭代器的方法和 while 實現上面 for 迴圈的邏輯,大致步驟如下:

  1. 獲取給定可迭代物件的迭代器;
  2. 呼叫迭代器的 next() 方法獲取下一項;
  3. 對當前項資料進行處理;
  4. 如果捕獲到 StopIteration ,那麼就停止迴圈
def funky_for_loop(iterable, action_to_do):
    iterator = iter(iterable)
    while not done_looping:
        try:
            item = next(iterator)
        except StopIteration:
            break
        else:
            action_to_do(item)

Python 底層的迴圈工作方式基本上如上程式碼,就是迭代器驅動的 for 迴圈。

再次回到迴圈陷阱

陷阱 1:耗盡的迭代器

陷阱 1 中,因為生成器是迭代器,迭代器是惰性的,也是一次性的,在已經遍歷過一次的情況下,再對其求和,返回的就是一個 0。

陷阱 2:部分消耗迭代器

陷阱 2 中,我們兩次詢問 9 是否存在於同一個生成器中,得到了不同的答案。

這是因為,第一次詢問時,Python 已經對這個生成器進行了遍歷,也就是呼叫 next() 函式查詢 9,找到後就會返回 True,第二次再詢問 9 是否存在時,會從上次的位置繼續 next() 查詢。

>>> nums = [1,2,3,4,5]
>>> squares = (n**2 for n in nums)
>>> 9 in squares
True
# 此時打印出來
>>> list(squares)
[16, 25]

陷阱 3:拆包是迭代

當直接在字典上迭代時,得到的是鍵:

>>> counts = {1:'a',2:'b'}
>>> for i in counts:
...     print(i)
... 
1
2

而對字典拆包時,和在字典上遍歷是一樣的,都是依賴於迭代器協議,因此得到的也是鍵。

總結

序列是迭代器,但是不是所有的迭代器都是序列。迭代器不可以被迴圈遍歷兩次、不能訪問其長度,也不能使用索引。

迭代器是 Python 中最基本的可迭代形式。如果你想在程式碼中做一個惰性迭代,請考慮迭代器,並考慮使用生成器函式或生成器表示式。

最後,請記住,Python 中的每一種迭代都依賴於迭代器協議,因此理解迭代器協議是理解 Python 中的迴圈的關鍵。

福利時刻

最近蒐集到慕課網視訊,800G 左右,視訊內容涵蓋 Python、Java、PHP、前端、小程式、演算法、架構、資料庫等等!關注本公眾號,後臺回覆「慕課網」即可獲取下載地址。

如果覺得有用,歡迎關注我的微信,一起學習,共同進步,不定期推出贈書活動~

你的關注是對我最大的鼓勵!

相關推薦

Python 迴圈陷阱

Python 中的 for 迴圈和其他語言中的 for 迴圈工作方式是不一樣的,今天就帶你深入瞭解 Python 的 for 迴圈,看看它是如何工作的,以及它為什麼按照這種方式工作。 迴圈中的陷阱 我們先來看一下 Python 迴圈中的「陷阱」,在我

python 迴圈顯示進度條

import sys # 迴圈時顯示進度條 # total 代表迴圈總數 ,num為當前迴圈數 def view_bar(num, total): rate = float(num) / float(total) rate_num = int(rate * 100)

python迴圈的pass、continue以及

說明 pass、continue以及break常用於控制迴圈的執行。它們的功能如下: pass:即“通過”的意思,程式執行至pass處會繼續執行後續的部分 continue:即“繼續”的意思,程式執行

獲取Python的簡單for迴圈索引的問題

    Python的for迴圈相比其他語言而言更加簡單,比如經常會有如下這樣類似的例子。我們可以直接對列表進行遍歷,獲取列表的某個元素,並對這個元素進行相應的操作。 testList = ['nice', 'to', 'meet', 'you'] for x in test

lambda表示式 Python 之 for迴圈的lambda

Python 之 for迴圈中的lambda 第一種 f = [lambda x: x*i for i in range(4)]  (如果將x換成i,呼叫時候就不用傳引數,結果都為3) 對於上面的表示式,呼叫結果: >>> f = [lambda x:

Python語言程式設計之一--for迴圈累加變數是否要清零

最近學到了Pyhton中迴圈這一章。之前也斷斷續續學過,但都只是到了函式這一章就停下來了,寫過的程式碼雖然儲存了下來,但是當時的思路和總結都沒有記錄下來,很可惜。這次我開通了部落格,就是要把這些珍貴的學習思考總結記錄下來。從現在開始。 關於這一章始終有幾個難點不懂。第一個就是每次迴圈過後,其中的累加變數是否

python 如何在一個for迴圈遍歷兩個列表

#coding:utf-8 ################# # for迴圈兩個列表的過程 list1 = ['1', '1'] list2 = ['A','B'] for x in list1, list2: reslut = x[:] print reslut # type=

JavaScriptfor..in迴圈陷阱

大家都知道在JavaScript中提供了兩種方式迭代物件:   (1)for 迴圈;   (2)for..in迴圈; 使用for迴圈進行迭代陣列物件,想必大家都已經司空見慣了。但是,使用for.. in迴圈時,大家可要注意了,為什麼這麼說呢?大家聽我娓娓道來.... jav

pythonfor迴圈使用range、len、enumerate函式的操作例項

#coding=utf8 print ''' Python中的for迴圈更像shell腳本里的foreach迭代。 Python中的for接受可迭代物件作為其引數,每次迭代其中一個元素。 Pytho

Python While迴圈語句 Python 程式設計 while 語句用於迴圈執行程式,即在某條件下,迴圈執行某段程式,以處理需要重複處理的相同任務。其基本形式為: while 判斷條件:

Python While迴圈語句 Python 程式設計中 while 語句用於迴圈執行程式,即在某條件下,迴圈執行某段程式,以處理需要重複處理的相同任務。其基本形式為: while判斷條件:執行語句…… 執行語句可以是單個語句或語句塊。判斷條件可以是任何表示式,任何

Python 在列表迴圈的一些坑

迴圈內用 remove 刪除列表自身元素 問題 在 for i in list 迴圈中,如果在迴圈內部使用 list 的 remove 方法刪除多個相鄰的資料時,會出現漏刪和輸出資訊錯誤; 當刪除一個數據時,會出現輸出資訊錯誤。 例如: # 建立

Python反射的作用

屬性 cnblogs argv 字符串 實現 span div init asa #coding:utf-8 ‘‘‘ #反射的基本用法: hasattr 判斷一個方法是否存在於實例中 getattr 指定一個方法名字,獲取該方法的內存地址,加"()"括號並可執行 ‘‘‘

python selenium調用js

python 混合 雙引號 urb pos exec tel nts .get python 中js中單引號和雙引號混合編程 js = ‘document.getElementsByName("m:ybzbxmbd:b_BIANHAO")[0].setAttribute("

確定當前Python環境的site-packages目錄位置

body 系統 nbsp import 結構 syntax imp bsp sco How to find the site-packages folder in Python? 在Mac OS或者Linux中比較有用,因為這些系統一般都同時安裝了多個版本。尤其是Mac O

Python 字典一鍵對應多個值

列表 檢查 span mark blog class efault remove one #字典的一鍵多值 print‘方案一 list作為dict的值 值允許重復‘ d1={} key=1 value=2 d1.setdefault(key

小白之Python-基礎的基礎02

分界線 一次 系統 註意 解析 正在 版本 輸入 name Python-基礎中的基礎02 繼續整理筆記,反復練習!fighting! -----------------華麗的分界線-------------變量:第一次出現叫做定義變量,再次出現為為該變量賦值>&

python列表的所有值轉換為字符串,以及列表拼接成一個字符串

bsp nbsp python class pan code blog 字符 for >>> ls1 = [‘a‘, 1, ‘b‘, 2] >>> ls2 = [str(i) for i in ls1] >>> ls2

python--Matplotlib顯示負號問題

bsp class otl rcp htm spa tar python 代碼 解決Matplotlib繪圖中,負號不正常顯示問題。 添加兩行代碼: 1 import matplotlib 2 3 matplotlib.rcParams[‘axes.unico

Python使用錯誤(持續更新.....)

clas div req mage 技術分享 執行 pan pytho ida 1、在使用requests發送請求,響應數據轉成json提示,沒有可解碼的json 解決辦法: 這是由於,發送請求的數據,存在錯誤,響應出錯比如404 400,所以找不到可以解碼的json 示例

Python自學之樂-python 2、python 3經典類、新式類的深度和廣度優先小結

python2 __init__ 寫上 print class ast python init 廣度優先 #Author:clarkclass Original(object):#在python 3 中寫上object的新式類和不寫的經典類遵循的都是廣度優先原則 de