午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

厲害了,小編看過(guò)的介紹迭代器和生成器最易懂、最全面的文章

 heii2 2019-03-14

作者:浪子燕青       鏈接:


迭代器:是訪問(wèn)數(shù)據(jù)集合內(nèi)元素的一種方式,一般用來(lái)遍歷數(shù)據(jù),但是他不能像列表一樣使用下標(biāo)來(lái)獲取數(shù)據(jù),也就是說(shuō)迭代器是不能返回的。

  1. Iterator:迭代器對(duì)象,必須要實(shí)現(xiàn)next魔法函數(shù)

  2. Iterable:可迭代對(duì)象,繼承Iterator,必須要實(shí)現(xiàn)iter魔法函數(shù)

比如:

from collections import Iterable,Iterator
a = [1,2,3]
print(isinstance(a,Iterator))
print(isinstance(a,Iterable))

返回結(jié)果:

False
True

在Pycharm中使用alt+b進(jìn)去list的源碼中可以看到,在list類中有iter魔法函數(shù),也就是說(shuō)只要實(shí)現(xiàn)了iter魔法函數(shù),那么這個(gè)對(duì)象就是可迭代對(duì)象。

上面的例子中a是一個(gè)列表,也是一個(gè)可迭代對(duì)象,那么如何才能讓這個(gè)a變成迭代器呢?使用iter()即可。

from collections import Iterable,Iterator
a = [1,2,3]
a = iter(a)
print(isinstance(a,Iterator))
print(isinstance(a,Iterable))
print(next(a))
print('----')
for x in a:
print(x)

返回結(jié)果:

True
True
1
----
2
3

可以看到現(xiàn)在a是可迭代對(duì)象又是一個(gè)迭代器,說(shuō)明列表a中有iter方法,該方法返回的是迭代器,這個(gè)時(shí)候使用next就可以獲取a的下一個(gè)值,但是要記住迭代器中的數(shù)值只能被獲取一次。

梳理迭代器(Iterator)與可迭代對(duì)象(Iterable)的區(qū)別:

  1. 可迭代對(duì)象:繼承迭代器對(duì)象,可以用for循環(huán)(說(shuō)明實(shí)現(xiàn)了iter方法)

  2. 迭代器對(duì)象:可以用next獲取下一個(gè)值(說(shuō)明實(shí)現(xiàn)了next方法),但是每個(gè)值只能獲取一次,單純的迭代器沒有實(shí)現(xiàn)iter魔法函數(shù),所以不能使用for循環(huán)

  3. 只要可以用作for循環(huán)的都是可迭代對(duì)象

  4. 只要可以用next()函數(shù)的都是迭代器對(duì)象

  5. 列表,字典,字符串是可迭代對(duì)象但是不是迭代器對(duì)象,如果想變成迭代器對(duì)象可以使用iter()進(jìn)行轉(zhuǎn)換

  6. Python的for循環(huán)本質(zhì)上是使用next()進(jìn)行不斷調(diào)用,for循環(huán)的是可迭代對(duì)象,可迭代對(duì)象中有iter魔法函數(shù),可迭代對(duì)象繼承迭代器對(duì)象,迭代器對(duì)象中有next魔法函數(shù)

  7. 一般由可迭代對(duì)象變迭代器對(duì)象

可迭代對(duì)象

可迭代對(duì)象每次使用for循環(huán)一個(gè)數(shù)組的時(shí)候,本質(zhì)上會(huì)從類中嘗試調(diào)用iter魔法函數(shù),如果類中有iter魔法函數(shù)的話,會(huì)優(yōu)先調(diào)用iter魔法函數(shù),當(dāng)然這里切記iter方法必須要返回一個(gè)可以迭代的對(duì)象,不然就會(huì)報(bào)錯(cuò)。

如果沒有定義iter魔法函數(shù)的話,會(huì)創(chuàng)建一個(gè)默認(rèn)的迭代器,該迭代器調(diào)用getitem魔法函數(shù),如果你沒有定義iter和getitem兩個(gè)魔法函數(shù)的話,該類型就不是可迭代對(duì)象,就會(huì)報(bào)錯(cuò)。

比如:

class s:
def __init__(self,x):
self.x = x
def __iter__(self):
return iter(self.x)
# 這里必須要返回一個(gè)可以迭代的對(duì)象
# def __getitem__(self, item):
# return self.x[item]
# iter和getitem其中必須要實(shí)現(xiàn)一個(gè)
a = s('123')
# 這里的a就是可迭代對(duì)象
# 這里不能調(diào)用next(a)方法,因?yàn)闆]有定義
for x in a:
print(x)

這里把注釋符去掉返回結(jié)果也是一樣的,返回結(jié)果:

1
2
3

迭代器對(duì)象

一開始提起,iter搭配Iterable做可迭代對(duì)象,next搭配Iterator做迭代器。next()接受一個(gè)迭代器對(duì)象,作用是獲取迭代器對(duì)象的下一個(gè)值,迭代器是用來(lái)做迭代的,只會(huì)在需要的時(shí)候產(chǎn)生數(shù)據(jù)。

和可迭代對(duì)象不同,可迭代對(duì)象一開始是把所有的列表放在一個(gè)變量中,然后用getitem方法不斷的返回?cái)?shù)值,getitem中的item就是索引值。

但是next方法并沒有索引值,所以需要自己維護(hù)一個(gè)索引值,方便獲取下一個(gè)變量的位置。

class s:
def __init__(self,x):
self.x = x
# 獲取傳入的對(duì)象
self.index = 0
# 維護(hù)索引值
def __next__(self):
try:
result = self.x[self.index]
# 獲取傳入對(duì)象的值
except IndexError:
# 如果索引值錯(cuò)誤
raise StopIteration
# 拋出停止迭代
self.index += 1
# 索引值+1,用來(lái)獲取傳入對(duì)象的下一個(gè)值
return result
# 返回傳入對(duì)象的值

a = s([1,2,3])
print(next(a))
print('----------')
for x in a:
# 類中并沒有iter或者getitem魔法函數(shù),不能用for循環(huán),會(huì)報(bào)錯(cuò)
print(x)

返回結(jié)果:

Traceback (most recent call last):
1
----------
File 'C:/CODE/Python進(jìn)階知識(shí)/迭代協(xié)議/迭代器.py', line 34, in <module>
for x in a:
TypeError: 's' object is not iterable

上面一個(gè)就是完整的迭代器對(duì)象,他是根據(jù)自身的索引值來(lái)獲取傳入對(duì)象的下一個(gè)值,并不是像可迭代對(duì)象直接把傳入對(duì)象讀取到內(nèi)存中,所以對(duì)于一些很大的文件讀取的時(shí)候,可以一行一行的讀取內(nèi)容,而不是把文件的所有內(nèi)容讀取到內(nèi)存中。

這個(gè)類是迭代器對(duì)象,那么如何才能讓他能夠使用for循環(huán)呢?那就讓他變成可迭代對(duì)象,只需要在類中加上iter魔法函數(shù)即可。

class s:
def __init__(self,x):
self.x = x
# 獲取傳入的對(duì)象
self.index = 0
# 維護(hù)索引值
def __next__(self):
try:
result = self.x[self.index]
# 獲取傳入對(duì)象的值
except IndexError:
# 如果索引值錯(cuò)誤
raise StopIteration
# 拋出停止迭代
self.index += 1
# 索引值+1,用來(lái)獲取傳入對(duì)象的下一個(gè)值
return result
# 返回傳入對(duì)象的值
def __iter__(self):
return self
a = s([1,2,3])
print(next(a))
print('----------')
for x in a:
print(x)

返回結(jié)果:

1
----------
2
3

可以看到這個(gè)時(shí)候運(yùn)行成功,但是這個(gè)對(duì)象還是屬于迭代器對(duì)象,因?yàn)樵趎ext獲取下一個(gè)值會(huì)報(bào)錯(cuò)。

知識(shí)整理

根據(jù)上面的代碼提示,得到規(guī)律:

  1. iter讓類變成可迭代對(duì)象,next讓類變成迭代器(要維護(hù)索引值)。

  2. 可迭代對(duì)象可以用for循環(huán),迭代器可以用next獲取下一個(gè)值。

  3. 迭代器如果想要變成可迭代對(duì)象用for循環(huán),就要在迭代器內(nèi)部加上iter魔法函數(shù)

  4. 可迭代對(duì)象如果想要能用next魔法函數(shù),使用自身類中的iter()方法即可變成迭代器對(duì)象

class s:
def __init__(self,x):
self.x = x
self.index = 0
def __next__(self):
try:
result = self.x[self.index]
except IndexError:
raise StopIteration
self.index += 1
return result

class b:
def __init__(self,x):
self.x = x
def __iter__(self):
return s(self.x)
a = b([1,2,3])

for x in a:
print(x)

返回結(jié)果:

1
2
3

這個(gè)時(shí)候是不能再用next方法了,應(yīng)為類b是一個(gè)可迭代對(duì)象,并非迭代器,這個(gè)時(shí)候不能用next方法,但是可以讓類b繼承類s,這樣就能用next()方法獲取下一個(gè)值,但是你的類b中要存在索引值,不然會(huì)報(bào)錯(cuò),如下代碼:

class s:
def __init__(self,x):
self.x = x
# 獲取傳入的對(duì)象
self.index = 0
# 維護(hù)索引值
def __next__(self):
try:
result = self.x[self.index]
# 獲取傳入對(duì)象的值
except IndexError:
# 如果索引值錯(cuò)誤
raise StopIteration
# 拋出停止迭代
self.index += 1
# 索引值+1,用來(lái)獲取傳入對(duì)象的下一個(gè)值
return result
# 返回傳入對(duì)象的值
# def __iter__(self):
# return self
class b(s):
def __init__(self,x):
self.x = x
self.index = 0
def __iter__(self):
return s(self.x)
a = b([1,2,3])

print(next(a))
print(next(a))

返回結(jié)果:

1
2

可以這么做,但是沒必要,因?yàn)檫@樣違反了設(shè)計(jì)原則。

迭代器的設(shè)計(jì)模式

迭代器模式:提供一種方法順序訪問(wèn)一個(gè)聚合對(duì)象中的各種元素,而又不暴露該對(duì)象的內(nèi)部
表示。

迭代器的設(shè)計(jì)模式是一種經(jīng)典的設(shè)計(jì)模式,根據(jù)迭代器的特性(根據(jù)索引值讀取下一個(gè)內(nèi)容,不一次性讀取大量數(shù)據(jù)到內(nèi)存)不建議將next和iter都寫在一個(gè)類中去實(shí)現(xiàn)。

新建一個(gè)迭代器,用迭代器維護(hù)索引值,返回根據(jù)索引值獲取對(duì)象的數(shù)值,新建另一個(gè)可迭代對(duì)象,使用iter方法方便的循環(huán)迭代器的返回值。

生成器

生成器:函數(shù)中只要有yield,這個(gè)函數(shù)就會(huì)變成生成器。每次運(yùn)行到y(tǒng)ield的時(shí)候,函數(shù)會(huì)暫停,并且保存當(dāng)前的運(yùn)行狀態(tài),返回返回當(dāng)前的數(shù)值,并在下一次執(zhí)行next方法的時(shí)候,又從當(dāng)前位置繼續(xù)往下走。

簡(jiǎn)單用法

舉個(gè)例子:

def gen():
yield 1
# 返回一個(gè)對(duì)象,這個(gè)對(duì)象的值是1
def ret():
return 1
# 返回一個(gè)數(shù)字1
g = gen()
r = ret()
print(g,r)
print(next(g))

返回結(jié)果:

<generator object gen at 0x000001487FDA2D58> 1
1

可以看到return是直接返回?cái)?shù)值1,yield是返回的一個(gè)生成器對(duì)象,這個(gè)對(duì)象的值是1,使用next(g)或者for x in g:print x 都是可以獲取到他的內(nèi)容的,這個(gè)對(duì)象是在python編譯字節(jié)碼的時(shí)候就產(chǎn)生。

def gen():
yield 1
yield 11
yield 111
yield 1111
yield 11111
yield 111111
# 返回一個(gè)對(duì)象,這個(gè)對(duì)象內(nèi)的值是1和11,111...
def ret():
return 1
return 3
# 第二個(gè)return是無(wú)效的
g = gen()
r = ret()
print(g,r)
print(next(g))
for x in g:
print(x)

返回結(jié)果:

<generator object gen at 0x000002885FE32D58> 1
1
11
111
1111
11111
111111

就像迭代器的特性一樣,獲取過(guò)一遍的值是沒法再獲取一次的,并且不是那種一次把所有的結(jié)果求出放在內(nèi)存或者說(shuō)不是一次性讀取所有的內(nèi)容放在內(nèi)存中。

梳理特性:

  1. 使用yield的函數(shù)都是生成器函數(shù)

  2. 可以使用for循環(huán)獲取值,也可以使用next獲取生成器函數(shù)的值

原理

函數(shù)工作原理:函數(shù)的調(diào)用滿足“后進(jìn)先出”的原則,也就是說(shuō),最后被調(diào)用的函數(shù)應(yīng)該第一個(gè)返回,函數(shù)的遞歸調(diào)用就是一個(gè)經(jīng)典的例子。顯然,內(nèi)存中以“后進(jìn)先出”方式處理數(shù)據(jù)的棧段是最適合用于實(shí)現(xiàn)函數(shù)調(diào)用的載體,在編譯型程序語(yǔ)言中,函數(shù)被調(diào)用后,函數(shù)的參數(shù),返回地址,寄存器值等數(shù)據(jù)會(huì)被壓入棧,待函數(shù)體執(zhí)行完畢,將上述數(shù)據(jù)彈出棧。這也意味著,一個(gè)被調(diào)用的函數(shù)一旦執(zhí)行完畢,它的生命周期就結(jié)束了。

python解釋器運(yùn)行的時(shí)候,會(huì)用C語(yǔ)言當(dāng)中的PyEval_EvalFramEx函數(shù)創(chuàng)建一個(gè)棧幀,所有的棧幀都是分配再堆內(nèi)存上,如果不主動(dòng)釋放就會(huì)一直在里面。

Python 的堆棧幀是分配在堆內(nèi)存中的,理解這一點(diǎn)非常重要!Python 解釋器是個(gè)普通的 C 程序,所以它的堆棧幀就是普通的堆棧。但是它操作的 Python 堆棧幀是在堆上的。除了其他驚喜之外,這意味著 Python 的堆棧幀可以在它的調(diào)用之外存活。(FIXME: 可以在它調(diào)用結(jié)束后存活),這個(gè)就是生成器的核心原理實(shí)現(xiàn)。

Python腳本都會(huì)被python.exe編譯成字節(jié)碼的形式,然后python.exe再執(zhí)行這些字節(jié)碼,使用dis即可查看函數(shù)對(duì)象的字節(jié)碼對(duì)象。

import dis
# 查看函數(shù)程序字節(jié)碼
a = 'langzi'
print(dis.dis(a))
print('-'*20)
def sb(admin):
print(admin)
print(dis.dis(sb))

返回結(jié)果:

  1           0 LOAD_NAME                0 (langzi)
# 加載名字 為langzi
2 RETURN_VALUE
# 返回值
None
--------------------
15 0 LOAD_GLOBAL 0 (print)
# 加載一個(gè)print函數(shù)
2 LOAD_FAST 0 (admin)
# 加載傳遞參數(shù)為admin
4 CALL_FUNCTION 1
# 調(diào)用這個(gè)函數(shù)
6 POP_TOP
# 從棧的頂端把元素移除出來(lái)
8 LOAD_CONST 0 (None)
# 因?yàn)樵摵瘮?shù)沒有返回任何值,所以加載的值是none
10 RETURN_VALUE
# 最后把load_const的值返回(個(gè)人理解)
None

代碼函數(shù)運(yùn)行的時(shí)候,python將代碼編譯成字節(jié)碼,當(dāng)函數(shù)存在yield的時(shí)候,python會(huì)將這個(gè)函數(shù)標(biāo)記成生成器,當(dāng)調(diào)用這個(gè)函數(shù)的時(shí)候,會(huì)返回生成器對(duì)象,調(diào)用這個(gè)生成器對(duì)象后C語(yǔ)言中寫的函數(shù)會(huì)記錄上次代碼執(zhí)行到的位置和變量。

再C語(yǔ)言中的PyGenObject中有兩個(gè)值,gi_frame(存儲(chǔ)上次代碼執(zhí)行到的位置f_lasti的上次代碼執(zhí)行到的變量f_locals),gi_code(存儲(chǔ)代碼),使用dis也可以獲取到上次代碼執(zhí)行的位置和值。

舉個(gè)例子:

import dis
def gen():
yield 1
yield 2
return 666

g = gen()
# g是生成器對(duì)象
print(dis.dis(g))
print('*'*10)
print(g.gi_frame.f_lasti)
# 這里還沒有執(zhí)行,返回的位置是-1
print(g.gi_frame.f_locals)
# 這里還沒有執(zhí)行,返回的對(duì)象是{}
next(g)
print('*'*10)
print(g.gi_frame.f_lasti)
print(g.gi_frame.f_locals)

返回結(jié)果:

 11           0 LOAD_CONST               1 (1)
# 加載值為1
2 YIELD_VALUE
4 POP_TOP

12 6 LOAD_CONST 2 (2)
8 YIELD_VALUE
10 POP_TOP

13 12 LOAD_CONST 3 (666)
14 RETURN_VALUE
None
**********
-1
# 因?yàn)檫€沒有執(zhí)行,所以獲取的行數(shù)為 -1
{}
**********
2
# 這里開始執(zhí)行了第一次,獲取的行數(shù)是2,2對(duì)應(yīng)2 YIELD_VALUE就是前面加載的數(shù)值1
{}
# g.gi_frame.f_locals 是局部變量,你都沒定義那么獲取的結(jié)果自然是{},你只需在代碼中加上user='admin',這里的{}就會(huì)改變。

生成器可以在任何時(shí)候被任何函數(shù)恢復(fù)執(zhí)行,因?yàn)樗臈瑢?shí)際上不在棧上而是在堆上。生成器在調(diào)用調(diào)用層次結(jié)構(gòu)中的位置不是固定的,也不需要遵循常規(guī)函數(shù)執(zhí)行時(shí)遵循的先進(jìn)后出順序。因?yàn)檫@些特性,生成器不僅能用于生成可迭代對(duì)象,還可以用于實(shí)現(xiàn)多任務(wù)協(xié)作。

就是說(shuō)只要拿到了這個(gè)生成器對(duì)象,就能對(duì)這個(gè)生成器對(duì)象進(jìn)行控制,比如繼續(xù)執(zhí)行暫停等待,這個(gè)就是協(xié)程能夠執(zhí)行的理論原理。

應(yīng)用場(chǎng)景

讀取文件,使用open(‘xxx’).read(2019)//打開一個(gè)文件,每次讀取2019個(gè)偏移量。文件a.txt是一行文字,但是特別長(zhǎng),這一行文字根據(jù)|符號(hào)分開,如何讀取?

寫入文件代碼:

# -*- coding:utf-8 -*-
import random
import threading
import string
import time
t1 = time.time()
def write(x):
with open('a.txt','a+')as a:
a.write(x + '||')

def run():
for x in range(10000000):
strs = str(random.randint(1000,2000)) +random.choice(string.ascii_letters)*10
write(strs)
for x in range(10):
t = threading.Thread(target=run)
t.start()
t2 = time.time()
print(t2 - t1)

讀取文件代碼:

# -*- coding:utf-8 -*-
def readbooks(f, newline):
# f為傳入的文件名,newline為分隔符
buf = ''
# 緩存,處理已經(jīng)讀出來(lái)的數(shù)據(jù)量
while 1:
while newline in buf:
# 緩存中的數(shù)據(jù)是否存在分隔符
pos = buf.index(newline)
# 如果存在就找到字符的位置,比如0或者1或者2
yield buf[:pos]
# 暫停函數(shù),返回緩存中的從頭到字符的位置
buf = buf[pos + len(newline):]
# 緩存變成了,字符的位置到末尾
chunk = f.read(2010 * 10)
# 讀取2010*10的字符
if not chunk:
# 已經(jīng)讀取到了文件結(jié)尾
yield buf
break
buf += chunk
# 加到緩存
with open('a.txt','r')as f:
for line in readbooks(f,'||'):
print(line)

(完)

    本站是提供個(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)論公約

    類似文章 更多