Python for循環(huán)中的陷阱詳解
前言
Python 中的 for 循環(huán)和其他語言中的 for 循環(huán)工作方式是不一樣的,今天就帶你深入了解 Python 的 for 循環(huán),看看它是如何工作的,以及它為什么按照這種方式工作。
循環(huán)中的陷阱
我們先來看一下 Python 循環(huán)中的「陷阱」,在我們了解了循環(huán)的工作方式后,再來看下這些陷阱到底是怎么出現(xiàn)的。
陷阱 1:循環(huán)兩次
現(xiàn)在我們先假設(shè)有一個(gè)數(shù)字組成的列表,和一個(gè)用于返回這些數(shù)字的平方的生成器:
>>> nums = [1, 2, 3, 4] >>> squares = (n**2 for n in nums)
我們可以將這個(gè)生成器對(duì)象傳遞給元組構(gòu)造器,從而可以得到一個(gè)元組:
>>> tuple(squares) (1, 4, 9, 16)
這個(gè)時(shí)候,如果我們?cè)賹⑦@個(gè)構(gòu)造器對(duì)象傳遞給 sum 函數(shù),按理說應(yīng)該會(huì)返回這些數(shù)字的和吧:
>>> sum(squares) 0
返回的是個(gè) 0,先拖住下巴。
陷阱 2:檢查是否包含
我們還是使用上面的數(shù)字列表和生成器:
>>> nums = [1, 2, 3, 4] >>> squares = (n**2 for n in nums)
如果我 squares 生成器中是否包含 9,答案是肯定的,若果我再問一次呢?
你敢答應(yīng)嗎
>>> 9 in squares True >>> 9 in squares False
發(fā)現(xiàn),第二次不靈了~
怎么不靈了
陷阱 3:拆包
現(xiàn)在假設(shè)有一個(gè)字典:
>>> counts = {1:'a', 2:'b'}
然后,我們用多個(gè)變量對(duì)字典進(jìn)行拆包:
>>> x,y = counts
你覺得這時(shí)候,x 和 y 中會(huì)是什么?
>>> x 1 >>> y 2
我們只得到了鍵。
下面,我們先來了解下 Python 中的循環(huán)工作原理,然后再反過頭來看這些陷阱問題。
一些概念
首先,先了解一些基本概念:
可迭代和序列
可迭代就是指任意可以使用 for 循環(huán)遍歷的東西,可迭代意味著可以遍歷,任何可以遍歷的東西都是可迭代的。
for item in some_iterable: print(item)
序列是一種常見的可迭代類型,如列表、元組、字符串等。
序列是可迭代的,它有著一些特點(diǎn),它們是從 0 開始索引,索引長(zhǎng)度不超過序列的長(zhǎng)度;它們有序列長(zhǎng)度;并且它們可以被切分。
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)
總結(jié)下來就是,任何可以用 for 循環(huán)遍歷的東西都是可迭代的,序列可迭代的類型中的一種,Python 還有著許多其他種類的可迭代類型。
迭代器
迭代器就是可以驅(qū)動(dòng)可迭代對(duì)象的東西。你可以從任何可迭代對(duì)象中獲得迭代器,你也可以使用迭代器來手動(dòng)對(duì)它的迭代進(jìn)行遍歷。
下面有三個(gè)可迭代對(duì)象:一個(gè)集合、一個(gè)元祖和一個(gè)字符串:
>>> nums = {1,2,3,4} >>> coors = (4,5,6) >>> words = "hello hoxis"
我們可以使用 Python 的內(nèi)置函數(shù) iter ,從這些可迭代對(duì)象中獲取到迭代器:
>>> iter(nums) <setiterator object at 0x7fa8c194ad70> >>> iter(coors) <tupleiterator object at 0x7fa8c1959610> >>> iter(words) <iterator object at 0x7fa8c19595d0>
一旦我們有了迭代器,我們就可以使用其內(nèi)置函數(shù) next() 來獲取它的下一個(gè)值:
>>> 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
若果迭代到頭了,也就是沒有下一個(gè)值了,就會(huì)拋出 StopIteration 異常。也就是說,它不會(huì)繼續(xù)循環(huán)取獲取第一個(gè)值。
是不是有點(diǎn)懵逼了?
- 可迭代對(duì)象是可以迭代的東西
- 迭代對(duì)象器實(shí)際上是遍歷可迭代對(duì)象的代理
- 迭代器沒有長(zhǎng)度,它們不能被索引。
- 可以使用迭代器來做的唯一有用的事情是將其傳遞給內(nèi)置的 next 函數(shù),或者對(duì)其進(jìn)行循環(huán)遍歷
- 可以使用 list() 函數(shù)將迭代器轉(zhuǎn)換為列表
>>> nums = {1,2,3,4} >>> num_iter = iter(nums) >>> next(num_iter) 1 >>> list(num_iter) [2, 3, 4] >>> list(num_iter) []
若果想再次將其轉(zhuǎn)換為列表,明顯地,得到的是一個(gè)空列表。
其實(shí)這也是迭代器的一個(gè)重要特性:惰性,只能使用一次,只能循環(huán)遍歷一次。并且,在我們調(diào)用 next() 函數(shù)之前,它不會(huì)做任何事情。因此,我們可以創(chuàng)建無限長(zhǎng)的迭代器,而創(chuàng)建無限長(zhǎng)的列表則不行,那樣會(huì)耗盡你的內(nèi)存!
可迭代對(duì)象不一定是迭代器,但是迭代器一定是可迭代的:
對(duì)象 | 可迭代? | 迭代器? |
---|---|---|
可迭代對(duì)象 | √ | 不一定 |
迭代器 | √ | √ |
生成器 | √ | √ |
列表 | √ | × |
其實(shí),Python 中有許多迭代器,生成器是迭代器,Python 的許多內(nèi)置類型也是迭代器。例如,Python 的 enumerate 和 reversed 對(duì)象就是迭代器。zip, map 和 filter 也是迭代器;文件對(duì)象也是迭代器。
Python 中的 for 循環(huán)
其實(shí),Python 并沒有傳統(tǒng)的 for 循環(huán),什么是傳統(tǒng)的 for 循環(huán)?
我們看下 Java 中的 for 循環(huán):
int[] integers = {1, 2, 3, 4}; for (int j = 0; j<integers.length; j++) { int i = integers[j]; System.out.println(i); }
這是一種 C風(fēng)格 的 for 循環(huán),JavaScript、C、C++、Java、PHP 和一大堆其他編程語言都有這種風(fēng)格的 for 循環(huán),但是 Python 確實(shí)沒有。
Python 中的我們稱之為 for 循環(huán)的東西,確切的說應(yīng)該是 foreach 循環(huán):
numbers = [1, 2, 3, 5, 7] for n in numbers: print(n)
和 C風(fēng)格 的 for 循環(huán)不同之處在于,Python 的 for 循環(huán)沒有索引變量,沒有索引變量的初始化,邊界檢查和索引變量的增長(zhǎng)。
這就是 Python 的 for 循環(huán)的不同之處!
使用索引?
你可能會(huì)懷疑,Python 的 for 循環(huán)是否在底層使用了索引,下面我們手動(dòng)的使用 while 循環(huán)和索引來遍歷:
>>> nums = [1,2,3,4] >>> i = 0 >>> while i < len(nums): ... print(num[i]) ... i += 1 ... 0 1 2 3
對(duì)于列表,這樣遍歷是可以的,但不代表適用于所有可迭代對(duì)象,它只適用于序列。
比如,我們對(duì)一個(gè) set 使用這種方法遍歷,會(huì)得到一個(gè)異常:
>>> 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
因?yàn)?set 不是序列,因此不支持索引遍歷。
我們不能使用索引手動(dòng)對(duì) Python 中的每一個(gè)迭代對(duì)象進(jìn)行遍歷。對(duì)于那些不是序列的迭代器來說,更是行不通的。
實(shí)現(xiàn)沒有 for 的循環(huán)
從上文可以看出,Python 中的 for 循環(huán)不使用索引,它使用的是迭代器。讓我們來看下它是如何工作的。
通過上文,我們了解到了迭代器和 iter、next 函數(shù),現(xiàn)在我們可以嘗試不用 for 循環(huán)來遍歷一個(gè)可迭代對(duì)象。
下面是一個(gè)正常的 for 循環(huán):
def funky_for_loop(iterable, action_to_do): for item in iterable: action_to_do(item)
我們要嘗試用迭代器的方法和 while 實(shí)現(xiàn)上面 for 循環(huán)的邏輯,大致步驟如下:
- 獲取給定可迭代對(duì)象的迭代器;
- 調(diào)用迭代器的 next() 方法獲取下一項(xiàng);
- 對(duì)當(dāng)前項(xiàng)數(shù)據(jù)進(jìn)行處理;
- 如果捕獲到 StopIteration ,那么就停止循環(huán)
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 底層的循環(huán)工作方式基本上如上代碼,就是迭代器驅(qū)動(dòng)的 for 循環(huán)。
再次回到循環(huán)陷阱
陷阱 1:耗盡的迭代器
陷阱 1 中,因?yàn)樯善魇堑?,迭代器是惰性的,也是一次性的,在已?jīng)遍歷過一次的情況下,再對(duì)其求和,返回的就是一個(gè) 0。
陷阱 2:部分消耗迭代器
陷阱 2 中,我們兩次詢問 9 是否存在于同一個(gè)生成器中,得到了不同的答案。
這是因?yàn)椋谝淮卧儐枙r(shí),Python 已經(jīng)對(duì)這個(gè)生成器進(jìn)行了遍歷,也就是調(diào)用 next() 函數(shù)查找 9,找到后就會(huì)返回 True,第二次再詢問 9 是否存在時(shí),會(huì)從上次的位置繼續(xù) next() 查找。
>>> nums = [1,2,3,4,5] >>> squares = (n**2 for n in nums) >>> 9 in squares True # 此時(shí)打印出來 >>> list(squares) [16, 25]
陷阱 3:拆包是迭代
當(dāng)直接在字典上迭代時(shí),得到的是鍵:
>>> counts = {1:'a',2:'b'} >>> for i in counts: ... print(i) ... 1 2
而對(duì)字典拆包時(shí),和在字典上遍歷是一樣的,都是依賴于迭代器協(xié)議,因此得到的也是鍵。
總結(jié)
序列是迭代器,但是不是所有的迭代器都是序列。迭代器不可以被循環(huán)遍歷兩次、不能訪問其長(zhǎng)度,也不能使用索引。
迭代器是 Python 中最基本的可迭代形式。如果你想在代碼中做一個(gè)惰性迭代,請(qǐng)考慮迭代器,并考慮使用生成器函數(shù)或生成器表達(dá)式。
最后,請(qǐng)記住,Python 中的每一種迭代都依賴于迭代器協(xié)議,因此理解迭代器協(xié)議是理解 Python 中的循環(huán)的關(guān)鍵。
原文鏈接:https://opensource.com/article/18/3/loop-better-deeper-look-iteration-python
好了以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
PyCharm2021最新激活碼+激活碼補(bǔ)丁(親測(cè)最新版PyCharm2021.3激活成功)
這篇文章主要介紹了PyCharm2021最新激活碼+激活碼補(bǔ)丁,親測(cè)最新版PyCharm2021.3激活成功,PyCharm2020激活成功2020-09-09python實(shí)現(xiàn)多線程及線程間通信的簡(jiǎn)單方法
這篇文章主要為大家介紹了python實(shí)現(xiàn)多線程及線程間通信的簡(jiǎn)單方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07Python通過Pillow實(shí)現(xiàn)圖片對(duì)比
這篇文章主要介紹了Python Pillow實(shí)現(xiàn)圖片對(duì)比,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-0410個(gè)殺手級(jí)應(yīng)用的Python自動(dòng)化腳本
重復(fù)的任務(wù)總是耗費(fèi)時(shí)間和枯燥的。如果逐一裁剪100張照片,或者做諸如Fetching APIs、糾正拼寫和語法等任務(wù),所有這些都需要大量的時(shí)間。為什么不把它們自動(dòng)化呢?本文詳細(xì)介紹了10個(gè)Python自動(dòng)化腳本,感興趣的小伙伴可以閱讀一下2023-03-03flask-SQLALchemy連接數(shù)據(jù)庫的實(shí)現(xiàn)示例
sqlalchemy是數(shù)據(jù)庫的orm框架,讓我們操作數(shù)據(jù)庫的時(shí)候不要再用sql語句了,本文就介紹了flask-SQLALchemy連接數(shù)據(jù)庫的實(shí)現(xiàn)示例,感興趣的可以了解一下2022-06-06python3+opencv3識(shí)別圖片中的物體并截取的方法
今天小編就為大家分享一篇python3+opencv3識(shí)別圖片中的物體并截取的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-12-12Python可視化mhd格式和raw格式的醫(yī)學(xué)圖像并保存的方法
今天小編就為大家分享一篇Python可視化mhd格式和raw格式的醫(yī)學(xué)圖像并保存的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-01-01