日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

python入門系列:迭代器和生成器

 xiaoyimin 2019-02-13

python的迭代協(xié)議

引言

迭代器是訪問(wèn)集合內(nèi)部元素的一種方式,一般用來(lái)遍歷數(shù)據(jù)。

迭代器和用下標(biāo)索引訪問(wèn)的方式不一樣,迭代器是不能直接返回值的。

迭代器提供了一種惰性訪問(wèn)數(shù)據(jù)的方式,需要的時(shí)候才產(chǎn)生數(shù)據(jù)。

可迭代類型都實(shí)現(xiàn)了迭代協(xié)議,實(shí)際上就是__iter__()這個(gè)魔法函數(shù)。

可迭代類型和迭代器

前面講過(guò),collections.abc模塊中定義了很多內(nèi)置的抽象基類,現(xiàn)在我們重點(diǎn)關(guān)注其中的兩個(gè):Iterable 和 Iterator

Iterable

里面定義了一個(gè)抽象方法,__iter__(),也就是說(shuō)某個(gè)類只要實(shí)現(xiàn)了這個(gè)魔法函數(shù),它就是可迭代的類型

Iterator

首先,Iterator繼承了Iterable,在它的基礎(chǔ)上,又增加了一個(gè)抽象方法:__next__(),它是用來(lái)讓迭代器獲取下一個(gè)元素。

小結(jié)

可迭代類型和迭代器并不一樣,前者只需要實(shí)現(xiàn)__iter__()函數(shù),而對(duì)后者而言,__next__()才是它的核心。

比如list類型,它是一個(gè)可迭代類型,但并不是一個(gè)迭代器。a = [1, 2, 3]

print(isinstance(a, Iterable), isinstance(a, Iterator))

# result:

# True False

補(bǔ)充

在魔法函數(shù)那一小節(jié),我們講過(guò)這樣一個(gè)例子:class Language(object):

def __init__(self, language_list):

self.lans = language_list

def __getitem__(self, item):

return self.lans[item]

language = Language(['Python', 'C', 'Lisp'])

for lan in language:

print(lan)

# result:

# Python

# C

# Lisp

在Language這個(gè)類中,我們定義的是__getitem__這個(gè)魔法函數(shù),然后對(duì)這個(gè)類產(chǎn)生的實(shí)例我們可以使用for來(lái)遍歷元素了。也就是說(shuō)它成為了一個(gè)可迭代類型,但它并沒(méi)有實(shí)現(xiàn)剛才我們討論的__iter__()函數(shù)。

這是因?yàn)?,在Python內(nèi)部,很多地方做了兼容處理,當(dāng)我們是用for進(jìn)行迭代遍歷,解釋器首先會(huì)尋找__iter__()函數(shù),如果沒(méi)有,它就會(huì)退一步去尋找__getitem__(),這個(gè)是序列類型中會(huì)實(shí)現(xiàn)的一個(gè)魔法函數(shù),只要它接收從 0 開(kāi)始的整數(shù)為參數(shù),這個(gè)對(duì)象也是會(huì)被當(dāng)做可迭代類型的。

實(shí)際上,僅僅滿足了可迭代類型還不夠,真正能進(jìn)行迭代取值的是迭代器。通過(guò)iter()函數(shù),我們可以返回一個(gè)可迭代對(duì)象的迭代器,有了它才能進(jìn)行迭代取值。class Language(object):

def __init__(self, language_list):

self.lans = language_list

def __getitem__(self, item):

return self.lans[item]

language = Language(['Python', 'C', 'Lisp'])

my_iterator = iter(language)

print(my_iterator)

# result:

#

如果我們不實(shí)現(xiàn)__iter__()或__getitem__(),獲取迭代器的過(guò)中會(huì)出錯(cuò)class Language(object):

def __init__(self, language_list):

self.lans = language_list

language = Language(['Python', 'C', 'Lisp'])

my_iterator = iter(language)

print(my_iterator)

# result:

# TypeError: 'Language' object is not iterable

有了迭代器,迭代取值需要另外一個(gè)函數(shù)next(),每調(diào)用一次,就會(huì)返回一個(gè)值,直到拋出一個(gè)迭代結(jié)束的異常。class Language(object):

def __init__(self, language_list):

self.lans = language_list

def __getitem__(self, item):

return self.lans[item]

language = Language(['Python', 'C', 'Lisp'])

my_iterator = iter(language)

print(next(my_iterator))

print(next(my_iterator))

print(next(my_iterator))

print(next(my_iterator))

# result:

# Python

# C

# Lisp

# StopIteration

上面的結(jié)果已經(jīng)很接近直接使用for進(jìn)行迭代了,但是,依賴__getitem__()函數(shù),底層還是隱藏了很多細(xì)節(jié),如果我們想純粹地通過(guò)__iter__()來(lái)實(shí)現(xiàn)迭代過(guò)程,要怎么做呢?

__iter__()用來(lái)返回一個(gè)迭代器,通過(guò)這個(gè)迭代器來(lái)迭代取值,對(duì)應(yīng)顯示調(diào)用iter()的邏輯。

__next__()用來(lái)讓迭代器取下一個(gè)值,對(duì)應(yīng)顯示調(diào)用next()的邏輯。

使用for的時(shí)候,這兩個(gè)魔法函數(shù)會(huì)被自動(dòng)調(diào)用,完成迭代取值過(guò)程。from collections.abc import Iterator

class MyIterator(Iterator):

def __init__(self, data_list):

self.iter_list = data_list

self.index = 0

def __next__(self):

# 這里是通過(guò)記錄索引,單次取值達(dá)到迭代目的

# 更好的方式是通過(guò)生成器來(lái)進(jìn)行迭代取值

try:

data = self.iter_list[self.index]

except IndexError:

raise StopIteration

self.index += 1

return data

class Language(object):

def __init__(self, language_list):

self.lans = language_list

def __iter__(self):

return MyIterator(self.lans)

language = Language(['Python', 'C', 'Lisp'])

for lan in language:

print(lan)

# result:

# Python

# C

# Lisp

生成器函數(shù)使用

引言

函數(shù)里面只要存在yield關(guān)鍵字,它就是生成器函數(shù)

生成器是惰性計(jì)算的一個(gè)關(guān)鍵

使用案例def gen_func():

yield 'MetaTian'

def func():

return 'MetaTian'

gen, res = gen_func(), func()

print(gen)

print(res)

# result:

#

# MetaTian

for val in gen:

print(val)

# result:

# MetaTian

第一個(gè)函數(shù)返回的是一個(gè)生成器對(duì)象,它是一個(gè)可迭代類型,因此,可以通過(guò)for進(jìn)行訪問(wèn)。def gen_func():

yield 1

yield 2

yield 3

gen = gen_func()

for val in gen:

print(val)

# result:

# 1

# 2

# 3

生成器的原理

Python中函數(shù)工作原理

對(duì)于編譯型語(yǔ)言,函數(shù)的調(diào)用會(huì)維持一個(gè)函數(shù)調(diào)用棧,某個(gè)函數(shù)執(zhí)行完成后,它就會(huì)被出棧處理,也就是說(shuō),函數(shù)執(zhí)行后,它的生命周期就結(jié)束了。

對(duì)于Python這樣的解釋型語(yǔ)言,函數(shù)的調(diào)用也要依賴棧結(jié)構(gòu),但是,函數(shù)對(duì)象是存放在堆內(nèi)存中的,也就意味著,一個(gè)函數(shù)被調(diào)用執(zhí)行了,它還在那兒。

什么是堆內(nèi)存和棧內(nèi)存?

解釋器用一個(gè)叫做PyEval_EvalFrameEx的C函數(shù)來(lái)執(zhí)行Python程序。對(duì)于一個(gè)Python中的函數(shù),解釋器接受一個(gè)棧幀(stack frame)對(duì)象,并在這個(gè)棧幀的上下文中執(zhí)行Python字節(jié)碼,完成函數(shù)調(diào)用。在字節(jié)碼執(zhí)行中,如果遇到了要調(diào)用其他函數(shù)的指令,解釋器會(huì)創(chuàng)建一個(gè)新的棧幀用來(lái)執(zhí)行新調(diào)用的函數(shù)。

生成器+函數(shù)def gen_func():

yield 1

name = 'MetaTian'

yield 2

return 'done'

在Python將函數(shù)編譯為字節(jié)碼時(shí),如果遇到y(tǒng)ield關(guān)鍵字,它就知道這是一個(gè)生成器函數(shù),內(nèi)部會(huì)做一個(gè)標(biāo)記。當(dāng)我們調(diào)用這個(gè)函數(shù)的時(shí)候,解釋器看到這個(gè)標(biāo)記后就會(huì)創(chuàng)建一個(gè)生成器,而不是去運(yùn)行它,后續(xù)函數(shù)的執(zhí)行交給生成器控制。

這個(gè)生成器內(nèi)部有兩個(gè)東西,一是對(duì)棧幀的引用,二是函數(shù)字節(jié)碼的引用。棧幀中有一個(gè)指針,指向最近執(zhí)行的那條指令,因?yàn)閳?zhí)行到和yield有關(guān)的字節(jié)碼后,函數(shù)會(huì)停止執(zhí)行,相當(dāng)于打了個(gè)斷點(diǎn),同時(shí)將yield后面的值返回。通過(guò)next(),可以讓函數(shù)繼續(xù)執(zhí)行(因?yàn)樯善饕彩堑鳎?,直到遇到下一個(gè)yield。

生成器對(duì)象也是分配在堆內(nèi)存中的,也就是說(shuō),只要我們?cè)诔绦蜻\(yùn)行的任何地方拿到了這個(gè)對(duì)象,都可以用它來(lái)控制函數(shù)的執(zhí)行。這也是后面攜程的一個(gè)理論基礎(chǔ)。重構(gòu)自己的可迭代類型

引言

前面將Language這個(gè)類,構(gòu)建成為了我們自定義的一個(gè)可迭代類型。

生成器也是迭代器,通過(guò)使用生成器,可以更簡(jiǎn)潔地達(dá)成目的。from collections.abc import Iterator

def gen_func():

yield 'MetaTian'

gen = gen_func()

print(isinstance(gen, Iterator))

# result:

# True

使用案例class Language(object):

def __init__(self, language_list):

self.lans = language_list

def __iter__(self):

i = 0

try:

while True:

val = self.lans[i]

yield val

i += 1

except IndexError:

return

language = Language(['Python', 'C', 'Lisp'])

for lan in language:

print(lan)

# result:

# Python

# C

# Lisp

總結(jié)

這里再來(lái)回顧一下前面講過(guò)的內(nèi)容,使用for遍歷的時(shí)候,首先會(huì)看作用對(duì)象是否為一個(gè)可迭代類型,如果是,那么會(huì)隱式調(diào)用__iter__(),得到一個(gè)迭代器對(duì)象,有了它,再隱式調(diào)用__next__()來(lái)不斷獲取下一個(gè)元素,直到

遇到一個(gè)停止迭代的異常。我們也可以通過(guò)內(nèi)置的兩個(gè)函數(shù)iter()和next()來(lái)人工干預(yù)迭代的過(guò)程。

在Language類中,__iter__()內(nèi)部加入了一個(gè)生成器的邏輯,結(jié)合前面的生成器函數(shù),可以知道,遇到y(tǒng)ield語(yǔ)句后,會(huì)產(chǎn)生一個(gè)生成器對(duì)象,由它來(lái)控制這個(gè)函數(shù)的后續(xù)執(zhí)行。

因?yàn)樯善饕彩堑?,所以__next__()的邏輯對(duì)它同樣適用,每次 next 都會(huì)在__iter__()函數(shù)中的while循環(huán)中不斷取值,直到拋出一個(gè)IndexError,迭代結(jié)束,__iter__()函數(shù)結(jié)束,for邏輯完成。生成器讀取大文件

引言

有一個(gè)數(shù)據(jù)文件,大小為 10GB

數(shù)據(jù)只有一行,行中有特殊的分隔符,現(xiàn)在需要剔除分隔符,獲得每一個(gè)被分隔的元素。比如,數(shù)據(jù)文件長(zhǎng)這樣:

dj134o0kgfdkjfkdjfk'6823sdkfslkfsldkfj'sdkfslfjyerojskfj...

是其中的分隔符

實(shí)現(xiàn)過(guò)程def extract(f, sep):

buff = ''

while True:

block = f.read(1024*4)

if not block: # 沒(méi)讀到內(nèi)容,說(shuō)明讀到尾部了

yield buff # 上次留下來(lái)的內(nèi)容

break

buff += block

while sep in buff:

cut = buff.index(sep) # 定位

yield buff[:cut] # 分隔符前的一個(gè)元素

buff = buff[cut + len(sep)] # 跳過(guò)分隔符和前面的元素

with open('data.txt') as f:

for line in extract(f, ''):

print(line)

喜歡python + qun:839383765 可以獲取Python各類免費(fèi)最新入門學(xué)習(xí)資料!

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多