推薦學習:
在剛開始學Python的時候,是不是經常會聽到大佬們在講容器、可迭代物件、迭代器、生成器、列表/集合/字典推導式等等眾多概念,其實這不是大佬們沒事就擱那扯專業術語來裝B,而是這些東西都得要明白的,光知道字串、列表等基礎還是不夠的,尤其是在Python的資料結構方面。
今天就來給大家講講Python中的容器、可迭代物件、迭代器和生成器這些難理解的概念,讓你的Python基礎更上一層樓!
在Python中,容器是把多種元素組織在一起的資料結構,容器中的元素就可以逐個迭代獲取。說白了,它的作用就像它的名字一樣:用來存放東西(資料)。
容器實際上是不存在的,它並不是一種資料型別,只是人為的一種概念,只是為了方便學習所創造的一個概念詞,它可以用成員關係操作符(in或not in)來判斷物件是否在容器裡面。
當然了,它不是我創造的,我沒有那麼大本事哈,是官方創造的好吧,你也不用擔心我是在教你一些奇奇怪怪的名詞,說出去別人都聽不懂…python中都是這麼叫的。常見的容器型別有列表(list)、元組(tuple)、字串(str)、字典(dict)以及集合(set )。
既然容器裡面的資料是可以迭代獲取的,那麼我們又得來學一個新概念:可迭代物件。
在python中,可迭代物件並不是指某種具體的資料型別,它是指儲存了元素的一個容器物件。
也就是說,如果容器裡面沒有儲存資料,那它就不是可迭代物件,並不是所有的容器都是可迭代物件,容器包含但並不僅限於可迭代物件。
注意兩個點:
1.很多容器都是可迭代物件(容器包含了可迭代物件)。 2.一個可迭代物件是不能獨立的進行迭代的,迭代是通過for來完成的,凡是可迭代物件都可以直接使用for迴圈進行存取。
for迴圈大家應該不陌生吧?有沒有想過,for迴圈內部是怎麼實現的?比如說這個for迴圈的例子,為什麼能輸出列表裡的每一個元素?它的內部是怎麼實現的?
其實for迴圈做了兩件事情:
1.使用 __iter__() 返回1個迭代器,迭代器在下面會講,這裡先知道有這麼個東西。 2.使用 __next__() 獲取迭代器中的每一個元素。
那麼我們不用for迴圈來輸出列表裡的每一個元素,
l = [1,2,3,4]# for i in l:# print(i)ite =l.__iter__() #接收一下ietr()幹了什麼print(ite) #列印print(ite.__next__()) #for迴圈幹第2件事情的時候做的第1步print(ite.__next__()) #for迴圈幹第2件事情的時候做的第2步print(ite.__next__()) #for迴圈幹第2件事情的時候做的第3步print(ite.__next__()) #for迴圈幹第2件事情的時候做的第4步
輸出結果:
可以看出來,如果我們去掉哪行列印ite的程式碼,執行效果就是跟for迴圈輸出列表裡面的每一個元素是一樣的,for迴圈裡面限定了範圍是4次,實際上就執行了1次__iter__()和4次__next__(),也就是說for迴圈存取迭代物件的本質就是通過這麼去實現的。
而且,for迴圈本質上乾的那兩件事情,缺一不可,也就是說如果沒有__iter__()先返回了迭代器,__next()__也無法獲取到元素,恰恰說明了前面說要注意的兩點中的第2點:一個可迭代物件是不能獨立的進行迭代的。
有兩個內建函數跟它們原理是一樣的,本質相同,一般要用的話用內建函數要方便一些,起碼不用寫那麼多下劃線:
內建函數 iter() 的本質是 __inter__() ,也是返回一個迭代器。 內建函數 next() 的本質是 __next__(),也是有了迭代器之後獲取元素。
可以看出來結果也是一模一樣的,既然講到了迭代器,那麼就來看看什麼是迭代器。
通過上面的for迴圈例子我們大概也能看得出來,
只要是實現了__iter__()和__next__()的物件,就是迭代器,迭代器是一個可迭代物件。 總之,迭代器是有__iter__()生成,可以通過__next__()進行呼叫。
既然如此,我們在學Python基礎的時候講過range()是一個可迭代物件,那麼它也是可以通過__iter__()生成一個迭代器的。
序列在【賦值語句】那個專題文章中我有提過,這裡再講一下,序列也是一個抽象的概念,它包含了列表、元組和字串,它本身是不存在的,也是便於學習所創造的一個概念詞。
可迭代物件包含序列,既然序列包含了列表、元組和字串,前面我們的例子中也涉及到 了,所以說序列可以被iter()和next()使用。
序列可以分為有限序列和無限序列。有限序列就是有範圍的,比如說range(10)就已經限定了範圍,相反的,無限序列也就是沒有限定範圍的序列。
我們來生成一個無限序列,這裡需要用到1個新模組itertools,itertools用於高效迴圈的迭代函數集合,它下面有一個方法count(),可生成迭代器且無範圍,可以理解為無限迭代器。
通過這個例子我們可以看出來,只要執行一次,next()就會獲取一次迭代器裡面的內容並逐次獲取,我這裡只寫了4個next(),你多寫幾次就會多輸出幾次。
像next()這種什麼時候需要就什麼時候呼叫的機制叫做懶載入機制,也叫懶漢式載入;
相反地就有餓漢式載入。比如for迴圈這種的,只要一執行就會把可迭代器裡面的所有物件都獲取。
列表推導式跟生成器有關,在講生成器之前,需要先知道什麼是列表推導式,列表推導式就是生成列表的一種方法,語法是這樣的:
l = [i for i in 可迭代物件]
i表示要放進列表裡的物件,for迴圈是一個式子。
比如我們用列表推導式來生成一個列表試試:
l = [i for i in range(5)]print(l)
執行結果:
[0, 1, 2, 3, 4]
運用列表推導式可以很方便地生成我們想要的列表。
同時它也有很多靈活的用法,比如在後面加上條件判斷
l = [i for i in range(5) if 4<5]print(l)
執行結果:
[0, 1, 2, 3, 4]
if後面的條件判斷為真,則可以正常生成列表,如果為假,則列表推導式是無效的,此時的l將是一個空列表。
還有其他靈活的用法,比如操作前面的i,比如讓i的數值全都翻2倍:
我們把迭代物件換一下,換成字串,也同樣可以輸出,只是*在字串裡面表示重複操作符,所以效果變成了這樣:
不僅如此,前面的i*2我們還可以用函數來進行操作,比如:
總而言之,列表推導式就是用來快速和自定義生成列表的一種方法,很靈活。
那麼有人可能會舉一反三了,列表推導式都是用 [] 來進行操作的,那如果用()來操作行嗎?它會不會生成一個元組?我們來看看:
[] 換成()之後,返回的是一個生成器generrator ,那麼下面我們再來講講生成器:
生成器是真實存在於Python中的物件,與容器這種概念詞是不同的,它是可以直接通過next()進行呼叫的。
第一種建立方法跟列表推導式是差不多的,就是 [] 換成了():
l = (i for i in 可迭代物件)
比如我們來生成一個生成器,看看能不能用next()直接呼叫:
l = (i for i in "abcd")print(next(l))
執行結果:
a
可以看出,生成器是可以直接呼叫的。那麼既然生成器可以被next()呼叫,那麼生成器就是一個特殊的迭代器,是一個可迭代物件。
除了用上面那種方法建立生成器,還可以用yield來建立,方法如下:
yield 關鍵字
比如說我們用一個函數中包含yield來建立生成器:
def fun(): a = 10 while 1: a += 1 yield a b = fun()print(b)
執行結果:
<generator object fun at 0x000001F2AD95E900>
結果就是生成了一個生成器,而且此時的函數fun()就已經不再是一個函數了,它是一個生成器,只要函數中出現了yield,函數就變成了生成器。
為什麼while迴圈沒有一直執行?先不著急,我們輸出看看:
def fun(): a = 10 while 1: a += 1 yield a b = fun()print(next(b))print(next(b))print(next(b))
執行結果:
111213
我呼叫了三次,所以它就執行了三次,while迴圈雖然存在,但是卻不起作用,是因為前面我們提過的懶漢式載入。
什麼時候需要了,什麼時候用next()呼叫,就是懶漢式載入,不像餓漢式載入那樣,提前生成了所有物件,如果這裡換成for迴圈來完成,比如:
def fun(): a = 10 while 1: a += 1 print(a)b = fun()
執行之後程式將會進入死迴圈,一直給a自加1,你可以試試看效果,這就是餓漢式載入提前生成了迭代器並呼叫了全部迭代器物件,餓漢式載入佔用資源的放大鏡。
今天講的內容可能聽起來比較枯燥,這也是沒得辦法的,有些東西第一次聽可能有點」難以下嚥「,見得多了之後就習慣了,你得強迫自己去試著接受和理解這些抽象的東西。
最後用一張圖來總結一下它們的關係:
推薦學習:
以上就是Python詳細解析之容器、可迭代物件、迭代器以及生成器的詳細內容,更多請關注TW511.COM其它相關文章!