3.0 Python 迭代器與生成器
當(dāng)我們需要處理一個(gè)大量的數(shù)據(jù)集合時(shí),一次性將其全部讀入內(nèi)存并處理可能會(huì)導(dǎo)致內(nèi)存溢出。此時(shí),我們可以采用迭代器Iterator
和生成器Generator
的方法,逐個(gè)地處理數(shù)據(jù),從而避免內(nèi)存溢出的問(wèn)題。
迭代器是一個(gè)可以逐個(gè)訪問(wèn)元素的對(duì)象,它實(shí)現(xiàn)了python
的迭代協(xié)議,即實(shí)現(xiàn)了__iter__()
和__next__()
方法。通過(guò)調(diào)用__next__()
方法,我們可以逐個(gè)訪問(wèn)迭代器中的元素,直到所有元素都被訪問(wèn)完畢,此時(shí)再次調(diào)用__next__()
方法會(huì)引發(fā)StopIteration
異常。
生成器是一種特殊的迭代器,它的實(shí)現(xiàn)方式更為簡(jiǎn)潔,即通過(guò)yield
語(yǔ)句來(lái)實(shí)現(xiàn)。生成器函數(shù)使用yield
語(yǔ)句返回值,當(dāng)生成器函數(shù)被調(diào)用時(shí),它會(huì)返回一個(gè)生成器對(duì)象,通過(guò)調(diào)用__next__()
方法來(lái)逐個(gè)訪問(wèn)生成器中的元素,直到所有元素都被訪問(wèn)完畢,此時(shí)再次調(diào)用__next__()
方法會(huì)引發(fā)StopIteration
異常。
使用迭代器和生成器可以有效地避免內(nèi)存溢出問(wèn)題,并且代碼實(shí)現(xiàn)也更為簡(jiǎn)潔、高效。在python中,很多內(nèi)置函數(shù)和語(yǔ)言特性都支持迭代器和生成器的使用,例如for循環(huán)、列表推導(dǎo)式、生成器表達(dá)式等。
3.1 使用迭代器
迭代器可以通過(guò)內(nèi)置函數(shù)iter()
進(jìn)行創(chuàng)建,同時(shí)可以使用next()
函數(shù)獲取下一個(gè)元素,如果迭代器沒(méi)有更多的元素,則拋出StopIteration
異常在for
循環(huán)中,迭代器可以自動(dòng)實(shí)現(xiàn)例如for x in my_iterable:
語(yǔ)句就可以遍歷my_iterable
對(duì)象的所有元素。此外python
中還有一種特殊的迭代器,稱為生成器(generator
),生成器是一種用簡(jiǎn)單的方法實(shí)現(xiàn)迭代器的方式,使用了yield
語(yǔ)句,生成器在執(zhí)行過(guò)程中可以暫停并繼續(xù)執(zhí)行,而函數(shù)則是一旦開(kāi)始執(zhí)行就會(huì)一直執(zhí)行到返回。
創(chuàng)建基本迭代器:?首先聲明列表,然后使用__iter__
將其轉(zhuǎn)為迭代器,并通過(guò)__next__
遍歷迭代對(duì)象.
list?=?[1,2,3,4,5,6,7,8,9,10]
>>>
item?=?list.__iter__()
type(item)
<class?'list_iterator'>
>>>
item.__next__()
1
next(item)
2
迭代器遍歷日志文件:?使用迭代器可以實(shí)現(xiàn)對(duì)文本文件或日志的遍歷,該方式可以遍歷大型文件而不會(huì)出現(xiàn)卡死現(xiàn)象.
#?手動(dòng)訪問(wèn)迭代器中的元素,可以使用next()函數(shù)
with?open("passwd.log")?as?fp:
????try:
????????????while?True:
????????????????????print(next(fp))
????except?StopIteration:
????????????print("none")
#?通過(guò)指定返回結(jié)束值來(lái)判斷迭代結(jié)束
with?open("passwd.log")?as?fp:
????while?True:
????????????line?=?next(fp,None)
????????????if?line?is?None:
????????????????????break
????????????print(line)
循環(huán)遍歷迭代元素:?由于迭代器遍歷結(jié)束會(huì)報(bào)錯(cuò),所以要使用try語(yǔ)句拋出一個(gè)StopIteration
結(jié)束異常.
listvar?=?["呂洞賓",?"張果老",?"藍(lán)采和",?"特乖離",?"和香菇",?"漢鐘離",?"王文"]
item?=?listvar.__iter__()
>>>
while?True:
????try:
????????????temp?=?next(item)
????????????print(temp)
????except?StopIteration:
????????????break
迭代器與數(shù)組之間互轉(zhuǎn):?通過(guò)使用enumerate方法,并將列表轉(zhuǎn)為迭代器對(duì)象,然后將對(duì)象轉(zhuǎn)為制定格式.
listvar?=?["呂洞賓",?"張果老",?"藍(lán)采和",?"特乖離",?"和香菇",?"漢鐘離",?"王文"]
>>>
iter?=?enumerate(listvar)??#?轉(zhuǎn)換為迭代器
dict?=?tuple(iter)?????????#?轉(zhuǎn)換為元組
dict
((0,?'呂洞賓'),?(1,?'張果老'),?(2,?'藍(lán)采和'),?(3,?'特乖離'),?(4,?'和香菇'),?(5,?'漢鐘離'),?(6,?'王文'))
>>>
dict?=?list(iter)
dict
[(0,?'呂洞賓'),?(1,?'張果老'),?(2,?'藍(lán)采和'),?(3,?'特乖離'),?(4,?'和香菇'),?(5,?'漢鐘離'),?(6,?'王文')]
3.2 使用生成器
生成器是一種可以動(dòng)態(tài)生成數(shù)據(jù)的迭代器,不同于列表等容器類(lèi)型一次性把所有數(shù)據(jù)生成并存儲(chǔ)在內(nèi)存中,生成器可以在需要時(shí)動(dòng)態(tài)生成數(shù)據(jù),這樣可以節(jié)省內(nèi)存空間和提高程序效率.使用生成器可以通過(guò)for循環(huán)遍歷序列、列表等容器類(lèi)型,而不需要提前知道其中所有元素.生成器可以使用yield
關(guān)鍵字返回值,每次調(diào)用yield
會(huì)暫停生成器并記錄當(dāng)前狀態(tài),下一次調(diào)用時(shí)可以從上一次暫停的地方繼續(xù)執(zhí)行,而生成器的狀態(tài)則保留在生成器對(duì)象內(nèi)部.除了使用next()
函數(shù)調(diào)用生成器外,還可以使用send()
函數(shù)向生成器中發(fā)送數(shù)據(jù),并在生成器內(nèi)部使用yield
表達(dá)式接收發(fā)送的數(shù)據(jù).
當(dāng)我們調(diào)用一個(gè)生成器函數(shù)時(shí),其實(shí)返回的是一個(gè)迭代器對(duì)象 只要表達(dá)式中使用了yield函數(shù),通常將此類(lèi)函數(shù)稱為生成器(generator) 運(yùn)行生成器時(shí),每次遇到y(tǒng)ield函數(shù),則會(huì)自動(dòng)保存并暫停執(zhí)行,直到使用next()方法時(shí),才會(huì)繼續(xù)迭代 跟普通函數(shù)不同,生成器是一個(gè)返回迭代器的函數(shù),只能用于迭代操作,更簡(jiǎn)單點(diǎn)理解生成器就是一個(gè)迭代器
在學(xué)習(xí)生成器之前,需要一些前置知識(shí),先來(lái)研究一下列表解析,列表解析是python迭代機(jī)制的一種應(yīng)用,它常用于實(shí)現(xiàn)創(chuàng)建新的列表,因此要放置于[]中,列表解析非常靈活,可以用戶快速創(chuàng)建一組相應(yīng)規(guī)則的列表元素,且支持迭代操作.
列表生成式基本語(yǔ)法:?通過(guò)列表生成式,我們可以完成數(shù)據(jù)的生成與過(guò)濾等操作.
ret?=?[item?for?item?in?range(30)?if?item?>0]
print(ret)
[1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20,?21,?22,?23,?24,?25,?26,?27,?28,?29]
>>>
ret?=?[item?for?item?in?range(30)?if?item?>3]
print(ret)
[4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20,?21,?22,?23,?24,?25,?26,?27,?28,?29]
>>>
ret?=?[item?for?item?in?range(30)?if?item%2!=0]
ret
[1,?3,?5,?7,?9,?11,?13,?15,?17,?19,?21,?23,?25,?27,?29]
列表式求階乘:?通過(guò)列表解析式,來(lái)實(shí)現(xiàn)列表的迭代求階乘,并且只打印大于2(if x>=2)
的數(shù)據(jù).
var?=?[1,2,3,4,5]
retn?=?[?item?**?2?for?item?in?var?]
retn
[1,?4,?9,?16,?25]
>>>
retn?=?[?item?**?2?for?item?in?var?if?item?>=?2?]
retn
[4,?9,?16,?25]
>>>
retn?=?[?(item**2)/2?for?item?in?range(1,10)?]
retn
[0.5,?2.0,?4.5,?8.0,?12.5,?18.0,?24.5,?32.0,?40.5]
數(shù)據(jù)轉(zhuǎn)換:?通過(guò)使用列表生成式,實(shí)現(xiàn)將一個(gè)字符串轉(zhuǎn)換成一個(gè)合格的列表.
String?=?"a,b,c,d,e,f,g,h"
List?=?[item?for?item?in?String.split(",")]
List
['a',?'b',?'c',?'d',?'e',?'f',?'g',?'h']
數(shù)據(jù)合并:?通過(guò)列表解析式,實(shí)現(xiàn)迭代將兩個(gè)列表按照規(guī)律合并.
temp1=["x","y","z"]
temp2=[1,2,3]
temp3=[?(i,j)?for?i?in?temp1?for?j?in?temp2?]
temp3
[('x',?1),?('x',?2),?('x',?3),?('y',?1),?('y',?2),?('y',?3),?('z',?1),?('z',?2),?('z',?3)]
文件過(guò)濾:?通過(guò)使用列表解析,實(shí)現(xiàn)文本的過(guò)濾操作.
import?os
file_list=os.listdir("/var/log")
file_log=[?i?for?i?in?file_list?if?i.endswith(".log")?]
print(file_log)
['boot.log',?'yum.log',?'ecs_network_optimization.log',?'ntp.log']
file_log=[?i?for?i?in?os.listdir("/var/log")?if?i.endswith(".log")?]
print(file_log)
['boot.log',?'yum.log',?'ecs_network_optimization.log',?'ntp.log']
接下來(lái)我們就來(lái)研究一下生成器吧,生成器類(lèi)似于返回值為數(shù)組的一個(gè)函數(shù),這個(gè)函數(shù)可以接受參數(shù),可以被調(diào)用,但不同于一般的函數(shù)會(huì)一次性返回包括了所有數(shù)值的數(shù)組,生成器一次只能產(chǎn)生一個(gè)值,這樣消耗的內(nèi)存數(shù)量將大大減小,而且允許調(diào)用函數(shù)可以很快的處理前幾個(gè)返回值,因此生成器看起來(lái)像是一個(gè)函數(shù),但是表現(xiàn)得卻像是迭代器.
我們先來(lái)看以下兩種情況的對(duì)比,第一種方法很簡(jiǎn)單,只有把一個(gè)列表生成式的[]中括號(hào)改為()小括號(hào),就創(chuàng)建了一個(gè)生成器.
lis?=?[x*x?for?x?in?range(10)]
print(lis)
[0,?1,?4,?9,?16,?25,?36,?49,?64,?81]
generator?=?(x*x?for?x?in?range(10))
print(generator)
<generator?object?<genexpr>?at?0x0000022E5C788A98>
如上例子,第一個(gè)lis通過(guò)列表生成式,創(chuàng)建了一個(gè)列表,而第二個(gè)generator
則打印出一個(gè)內(nèi)存地址,如果我們想獲取到第二個(gè)變量中的數(shù)據(jù),則需要迭代操作,如下所示:
generator?=?(x*x?for?x?in?range(10))
print(next(generator))
0
print(next(generator))
1
print(next(generator))
4
print(next(generator))
9
以上可以看到,generator保存的是算法,每次調(diào)用next(generaotr),就計(jì)算出他的下一個(gè)元素的值,直到計(jì)算出最后一個(gè)元素,使用for循環(huán)可以簡(jiǎn)便的遍歷出迭代器中的數(shù)據(jù),因?yàn)間enerator也是可迭代對(duì)象.
generator?=?(x*x?for?x?in?range(10))
for?i?in?generator:
????print(i,end="")
0149162536496481
生成器表達(dá)式并不真正創(chuàng)建數(shù)字列表,而是返回一個(gè)生成器對(duì)象,此對(duì)象在每次計(jì)算出一個(gè)條目后,把這個(gè)條目"產(chǎn)生"(yield)
出來(lái),生成器表達(dá)式使用了"惰性計(jì)算"或稱作"延遲求值"的機(jī)制序列過(guò)長(zhǎng),并且每次只需要獲取一個(gè)元素時(shí),應(yīng)當(dāng)考慮使用生成器表達(dá)式而不是列表解析.
import?sys
yie=(?i**2?for?i?in?range(1,10)?)
next(yie)
1
next(yie)
4
next(yie)
9
for?j?in?(?i**2?for?i?in?range(1,10)):print(j/2)
3.3 隊(duì)列的使用
隊(duì)列是一個(gè)多線程編程中常用的數(shù)據(jù)結(jié)構(gòu),它提供了一種可靠的方式來(lái)安全地傳遞數(shù)據(jù)和控制線程間的訪問(wèn). 在多線程環(huán)境下,如果沒(méi)有同步機(jī)制,多個(gè)線程同時(shí)訪問(wèn)共享資源,可能會(huì)導(dǎo)致數(shù)據(jù)混亂或者程序崩潰.而Queue隊(duì)列就是一種線程安全的數(shù)據(jù)結(jié)構(gòu),它提供了多個(gè)線程訪問(wèn)和操作的接口,可以保證多個(gè)線程之間的數(shù)據(jù)安全性和順序性. 通過(guò)Queue隊(duì)列,一個(gè)線程可以將數(shù)據(jù)放入隊(duì)列,而另一個(gè)線程則可以從隊(duì)列中取出數(shù)據(jù)進(jìn)行處理,實(shí)現(xiàn)了線程之間的通信和協(xié)調(diào).
先進(jìn)先出隊(duì)列:?先來(lái)介紹簡(jiǎn)單的隊(duì)列例子,以及隊(duì)列的常用方法的使用,此隊(duì)列是先進(jìn)先出模式.
import?queue
q?=?queue.Queue(5)????????????????????#默認(rèn)maxsize=0無(wú)限接收,最大支持的個(gè)數(shù)
print(q.empty())??????????????????????#查看隊(duì)列是否為空,如果為空則返回True
q.put(1)??????????????????????????????#PUT方法是,向隊(duì)列中添加數(shù)據(jù)
q.put(2)??????????????????????????????#第二個(gè)PUT,第二次向隊(duì)列中添加數(shù)據(jù)
q.put(3,block=False,timeout=2)????????#是否阻塞:默認(rèn)是阻塞block=True,timeout=超時(shí)時(shí)間
print(q.full())???????????????????????#查看隊(duì)列是否已經(jīng)放滿
print(q.qsize())??????????????????????#隊(duì)列中有多少個(gè)元素
print(q.maxsize)??????????????????????#隊(duì)列最大支持的個(gè)數(shù)
print(q.get(block=False,timeout=2))???#GET取數(shù)據(jù)
print(q.get())????????????????????????
q.task_done()???????#join配合task_done,隊(duì)列中有任務(wù)就會(huì)阻塞進(jìn)程,當(dāng)隊(duì)列中的任務(wù)執(zhí)行完畢之后,不在阻塞
print(q.get())
q.task_done()
q.join()????????????#隊(duì)列中還有元素的話,程序就不會(huì)結(jié)束程序,只有元素被取完配合task_done執(zhí)行,程序才會(huì)結(jié)束import?queue
def?show(q,i):
????if?q.empty()?or?q.qsize()?>=?1:
????????q.put(i)???#存隊(duì)列
????elif?q.full():
????????print('queue?not?size')
que?=?queue.Queue(5)???#允許5個(gè)隊(duì)列的隊(duì)列對(duì)象
for?i?in?range(5):
????show(que,i)
print('queue?is?number:',que.qsize())??#隊(duì)列元素個(gè)數(shù)
for?j?in?range(5):
????print(que.get())??#取隊(duì)列
print('......end')
后進(jìn)先出隊(duì)列:?這個(gè)隊(duì)列則是,后進(jìn)先出,也就是最后放入的數(shù)據(jù)最先彈出,類(lèi)似于堆棧.
import?queue
>>>
q?=?queue.LifoQueue()
>>>
q.put("wang")
q.put("rui")
q.put("ni")
q.put("hao")
>>>
print(q.get())
hao
print(q.get())
ni
print(q.get())
rui
print(q.get())
wang
print(q.get())
優(yōu)先級(jí)隊(duì)列:?此類(lèi)隊(duì)列,可以指定優(yōu)先級(jí)順序,默認(rèn)從高到低排列,以此根據(jù)優(yōu)先級(jí)彈出數(shù)據(jù).
import?queue
>>>
q?=?queue.PriorityQueue()
>>>
q.put((1,"python1"))
q.put((-1,"python2"))
q.put((10,"python3"))
q.put((4,"python4"))
q.put((98,"python5"))
>>>
print(q.get())
(-1,?'python2')
print(q.get())
(1,?'python1')
print(q.get())
(4,?'python4')
print(q.get())
(10,?'python3')
print(q.get())
(98,?'python5')
雙向的隊(duì)列:?雙向隊(duì)列,也就是說(shuō)可以分別從兩邊彈出數(shù)據(jù),沒(méi)有任何限制.
import?queue
>>>
q?=?queue.deque()
>>>
q.append(1)
q.append(2)
q.append(3)
q.append(4)
q.append(5)
>>>
q.appendleft(6)
>>>
print(q.pop())
5
print(q.pop())
4
print(q.popleft())
6
print(q.popleft())
1
print(q.popleft())
2
生產(chǎn)者消費(fèi)者模型:?生產(chǎn)者消費(fèi)者模型,是各種開(kāi)發(fā)場(chǎng)景中最常用的開(kāi)發(fā)模式,以下是模擬的模型.
import?queue,time
import?threading
q?=?queue.Queue()
def?productor(arg):
????while?True:
????????q.put(str(arg))
????????print("%s?號(hào)窗口有票...."%str(arg))
????????time.sleep(1)
def?consumer(arg):
????while?True:
????????print("第?%s?人取?%s?號(hào)窗口票"%(str(arg),q.get()))
????????time.sleep(1)
for?i?in?range(10):?????????????????????#負(fù)責(zé)生產(chǎn)票數(shù)
????t?=?threading.Thread(target=productor,args=(i,))
????t.start()
for?j?in?range(5):??????????????????????#負(fù)責(zé)取票,兩個(gè)用戶取票
????t?=?threading.Thread(target=consumer,args=(j,))
????t1?=?threading.Thread(target=consumer,args=(j,))
????t.start()
????t1.start()
本文作者: 王瑞 本文鏈接: https://www.lyshark.com/post/1c1ebaa1.html 版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-SA 許可協(xié)議。轉(zhuǎn)載請(qǐng)注明出處!