1. 程式人生 > >第五篇、Python之叠代器與生成器

第五篇、Python之叠代器與生成器

大數 path AI 依次 因此 以及 協程函數 模擬 操作

1、叠代和遞歸等概念

循環(loop):指的是在滿足條件的情況下,重復執行同一段代碼。比如,while語句,for循環。

叠代(iterate):指的是按照某種順序逐個訪問列表中的每一項。比如,for語句。Python中,叠代永遠是取出元素本身,而非元素的索引。對於有序集合,元素確實是有索引的。使用 enumerate() 函數獲得索引。

遞歸(recursion):指的是一個函數不斷調用自身的行為。比如,以編程方式輸出著名的斐波納契數列。

遍歷(traversal):指的是按照一定的規則訪問樹形結構中的每個節點,而且每個節點都只訪問一次。

2、叠代器協議

1) 叠代器協議是指:對象必須提供一個next方法

,執行該方法要麽返回叠代中的下一項,要麽就引起一個StopIteration異常,以終止叠代 (只能往後走不能往前退)

2) 可叠代對象:實現了叠代器協議的對象(如何實現:對象內部定義一個__iter__()方法)

3) 協議是一種約定,可叠代對象實現了叠代器協議,python的內部工具(如for循環,sum,min,max函數等)使用叠代器協議訪問對象。

註:可以通過__next__取值,就是叠代器(遵循叠代器協議生成的都是可叠代對象,叠代器就是可叠代對象)

l=["ye","ba","er","sun"]
inter_l=l.__iter__()
print(inter_l.__next__
()) print(next(inter_l))

3、python中強大的for循環機制

for循環的本質:循環所有對象,全都是使用叠代器協議。

for循環的原理:基於叠代器協議提供了一個統一的可以遍歷所有對象的方法,即在遍歷之前,先調用對象的__iter__方法將其轉換成一個叠代器,然後使用叠代器協議去實現循環訪問,這樣所有的對象就都可以通過for循環來遍歷了。

列表,字符串,元組,字典,集合,文件對象等本質上來說都不是可叠代對象,在使用for循環的時候內部是先調用他們內部的_iter_方法,使他們變成了可叠代對象,然後在使用可叠代對象的_next_方法依次循環元素,當元素循環完時,會觸發StopIteration異常,for循環會捕捉到這種異常,終止叠代。

訪問方式常見的有下標方式訪問、叠代器協議訪問、for循環訪問

技術分享圖片
l=[a,b,c]
#一:下標訪問方式
print(l[0])
print(l[1])
print(l[2])
# print(l[3])#超出邊界報錯:IndexError

#二:遵循叠代器協議訪問方式
diedai_l=l.__iter__()
print(diedai_l.__next__())
print(diedai_l.__next__())
print(diedai_l.__next__())
# print(diedai_l.__next__())#超出邊界報錯:StopIteration

#三:for循環訪問方式
#for循環l本質就是遵循叠代器協議的訪問方式,先調用diedai_l=l.__iter__()方法,或者直接diedai_l=iter(l),然後依次執行diedai_l.next(),直到for循環捕捉到StopIteration終止循環
  #for循環所有對象的本質都是一樣的原理

for i in l:#diedai_l=l.__iter__()
    print(i) #i=diedai_l.next()

#四:用while去模擬for循環做的事情
diedai_l=l.__iter__()
while True:
    try:
        print(diedai_l.__next__())
    except StopIteration:
        print(叠代完畢了,循環終止了)
        break
訪問方式

4. for循環的作用

對於序列類型的對象可使用下標的訪問方式,但是對於非序列類型(字典,集合,文件對象等),for循環提供了訪問遍歷機制。

技術分享圖片
l=[1,2,3]

index=0
while index < len(l):
    print(l[index])
    index+=1
View Code

while 需要加異常處理,for默認都已經內置。

文件默認就是叠代器:

技術分享圖片
f=open(a.txt,r)
f.__next__
f.__iter__
print(f)
print(f.__iter__())
 
for line in f: #f.__iter__()
    print(line)
 
i=f.__iter__()
 
while True:
    try:
        print(next(i))
    except StopIteration:
        break
View Code

5.生成器初識

生成器的本質:

可以理解為一種數據類型,這種數據類型自動實現了叠代器協議(其他的數據類型需要調用自己內置的__iter__方法),所以生成器就是可叠代對象。

生成器分類及在python中的表現形式:(Python有兩種不同的方式提供生成器)

1)生成器函數:常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行

2)生成器表達式:類似於列表推導,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表

生成器的優點:

Python使用生成器對延遲操作提供了支持。所謂延遲操作,是指在需要的時候才產生結果,而不是立即產生結果。這也是生成器的主要好處。

生成器小結:

1)是可叠代對象

2)現了延遲計算,省內存啊

3)生成器本質和其他的數據類型一樣,都是實現了叠代器協議,只不過生成器附加了一個延遲計算省內存的好處,其余的可叠代對象可沒有這點好處!!!

可叠代對象:只要對象本身有__iter__方法,那它就是可叠代的。 # 只有內置了iter方法就是可叠代的對象。

d={‘a‘:1,‘b‘:2,‘c‘:3}
d.__iter__() # iter(d)

執行對象下的__iter__方法,得到的結果就是叠代器 i=d.__iter__()

技術分享圖片
d={a:1,b:2,c:3}
i=iter(d)
while True:
    try:
        print(next(i))
    except StopIteration: # StopIteration 不能叫做錯誤,是一個結束信號。
        break
 
 
l=[a,b,c,d,e,f]  #列表也可以使用叠代器
i=iter(l) #i=l.__iter__()
while True:
    try:
        print(next(i))
    except StopIteration:
        break 

d={a:1,b:2,c:3}
d.__iter__
for k in d: #d.__iter__()    # for 循環
    print(k)
 
s={1,2,3,4}
for i in s:
    print(i)
View Code

為什麽要用叠代器:
優點:

  1. 叠代器提供了一種不依賴於索引的取值方式,這樣就可以遍歷那些沒有索引的可叠代對象了(字典,集合,文件)
  2. 叠代器與列表比較,叠代器是惰性計算的,更節省內存

缺點:

    1. 無法獲取叠代器的長度,使用不如列表索引取值靈活
    2. 一次性的,只能往後取值,不能倒著取值
from collections import Iterable,Iterator
s=hello
l=[1,2,3]
t=(1,2,3)
d={a:1}
set1={1,2,3,4}
f=open(a.txt)

s.__iter__()
l.__iter__()
t.__iter__()
d.__iter__()
set1.__iter__()
f.__iter__()                            # 都是可叠代的
print(isinstance(s,Iterable))           #True
print(isinstance(l,Iterable))           #True
print(isinstance(t,Iterable))           #True
print(isinstance(d,Iterable))           #True
print(isinstance(set1,Iterable))        #True
print(isinstance(f,Iterable))           #True

# 查看是否是叠代器
print(isinstance(s,Iterator))          #False
print(isinstance(l,Iterator))          #False
print(isinstance(t,Iterator))          #False
print(isinstance(d,Iterator))          #False
print(isinstance(set1,Iterator))       #False
print(isinstance(f,Iterator))          #True

6、 生成器函數

e.send與next(e)的區別:

  1. 如果函數內yield是表達式形式,那麽必須先next(e)
  2. 二者的共同之處是都可以讓函數在上次暫停的位置繼續運行,不一樣的地方在於send在觸發下一次代碼的執行時,會順便給yield傳一個值。
技術分享圖片
def lay_eggs(num):
    egg_list=[]
    for egg in range(num):
        egg_list.append(蛋%s %egg)
    return egg_list

yikuangdan=lay_eggs(10) #我們拿到的是蛋
print(yikuangdan)


def lay_eggs(num):
    for egg in range(num):
        res=蛋%s %egg
        yield res
        print(下完一個蛋)

laomuji=lay_eggs(10)#我們拿到的是一只母雞
print(laomuji)
print(laomuji.__next__())
print(laomuji.__next__())
print(laomuji.__next__())
egg_l=list(laomuji)
print(egg_l)
#演示只能往後不能往前
#演示蛋下完了,母雞就死了
下蛋

生成器與return有何區別?

return只能返回一次函數就徹底結束了,而yield能返回多次值。函數在暫停以及繼續下一次運行時的狀態是由yield保存。

yield把函數變成生成器-->叠代器

技術分享圖片
from collections import Iterator
#生成器就是一個函數,這個函數內包含有yield這個關鍵字
def test():
    print(one)
    yield 1 #return 1
    print(two)
    yield 2 #return 2
    print(three)
    yield 3 #return 2
    print(four)
    yield 4 #return 2
    print(five)
    yield 5 #return 2
 
g=test()
print(g)
print(isinstance(g,Iterator))
g.__iter__()
# g.__next__()
# res=next(g)
# print(res)
#
# res=next(g)
# print(res)
#
# res=next(g)
# print(res)
#
# res=next(g)
# print(res)
for i in g:
    print(i)
yield

next觸發函數的運行。函數變成叠代器,有執行效果,同時可以向外拉值。next一下函數執行一下。

技術分享圖片
def countdown(n):
    print(start coutdown)
    while n > 0:
        yield n #1
        n-=1
    print(done)
 
g=countdown(5)
# print(g)
 
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
 
# for i in g: #iter(g)
#     print(i)
 
# while True:
#     try:
#         print(next(g))
#     except StopIteration:
#         break
 
#
# def func():
#     n=0
#     while True:
#         yield n
#         n+=1
#
# f=func()
# print(next(f))
next 技術分享圖片
import time
def tail(file_path):
    with open(file_path,r) as f:
        f.seek(0,2)
        while True:
            line=f.readline()
            if not line:
                time.sleep(0.3)
                continue
            else:
                # print(line)
                yield line
tail(/tmp/a.txt)
View Code 技術分享圖片
#加顏色
g=tail(/tmp/a.txt)
 
for line in g:
    if error in line:
        print(\033[45m%s\033[0m %line)
View Code 技術分享圖片
#/usr/bin/env python
import time
#定義階段:定義倆生成器函數
def tail(file_path):
    with open(file_path,r) as f:
        f.seek(0,2)
        while True:
            line=f.readline()
            if not line:
                time.sleep(0.3)
#                print(‘====>‘)
                continue
            else:
                #print(line,end=‘‘)
                yield line
 
def grep(pattern,lines):
    for line in lines:
        if pattern in line:
            yield line
 
#調用階段:得到倆生成器對象
g1=tail(/tmp/a.txt)
g2=grep(error,g1)
 
#next觸發執行g2生成器函數
for i in g2:
    print(i)
View Code 技術分享圖片
#如果在一個函數內部yield的使用方式是表達式形式的話,如x=yield,那麽該函數成為協程函數
def eater(name):
    print(%s start to eat food %name)
    food_list=[]
    while True:
        food=yield food_list
        print(%s get %s ,to start eat %(name,food))
        food_list.append(food)
 
    print(done)
 
 
e=eater(鋼蛋)
# print(e)
 
print(next(e))
print(e.send(包子))
print(e.send(韭菜餡包子))
print(e.send(大蒜包子))
 
#為什麽叫協程?
#協程怎麽用?
協程函數

7、 生成器表達式(三元表達式)和列表解析

name=alex
name=linhaifeng
res=SB if name == alex else shuai
print(res)
技術分享圖片
egg_list=[雞蛋%s %i for i in range(10)] #列表解析

laomuji=(雞蛋%s %i for i in range(10)) #生成器表達式
print(laomuji)
print(next(laomuji)) #next本質就是調用__next__
print(laomuji.__next__())
print(next(laomuji))
View Code

總結:

1.把列表解析的[]換成()得到的就是生成器表達式

2.列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存

3.Python不但使用叠代器協議,讓for循環變得更加通用。大部分內置函數,也是使用叠代器協議訪問對象的。例如, sum函數是Python的內置函數,該函數使用叠代器協議訪問對象,而生成器實現了叠代器協議,所以,我們可以直接這樣計算一系列值的和:

sum(x ** 2 for x in xrange(4))

而不用多此一舉的先構造一個列表:

sum([x ** 2 for x in xrange(4)]) 

8、生成器總結

綜上已經對生成器有了一定的認識,下面我們以生成器函數為例進行總結

  • 語法上和函數類似:生成器函數和常規函數幾乎是一樣的。它們都是使用def語句進行定義,差別在於,生成器使用yield語句返回一個值,而常規函數使用return語句返回一個值
  • 自動實現叠代器協議:對於生成器,Python會自動實現叠代器協議,以便應用到叠代背景中(如for循環,sum函數)。由於生成器自動實現了叠代器協議,所以,我們可以調用它的next方法,並且,在沒有值可以返回的時候,生成器自動產生StopIteration異常
  • 狀態掛起:生成器使用yield語句返回一個值。yield語句掛起該生成器函數的狀態,保留足夠的信息,以便之後從它離開的地方繼續執行

優點一:生成器的好處是延遲計算,一次返回一個結果。也就是說,它不會一次生成所有的結果,這對於大數據量處理,將會非常有用。

技術分享圖片
sum([i for i in range(100000000)])   #列表解析:內存占用大,機器容易卡死
sum(i for i in range(100000000))     #生成器表達式:幾乎不占內存
View Code

優點二:生成器還能有效提高代碼可讀性

技術分享圖片
def index_words(text):
    result = []
    if text:
        result.append(0)
    for index, letter in enumerate(text, 1):
        if letter ==  :
            result.append(index)
    return result

print(index_words(hello alex da sb))
View Code 技術分享圖片
def index_words(text):
    if text:
        yield 0
    for index, letter in enumerate(text, 1):
        if letter ==  :
            yield index

g=index_words(hello alex da sb)
print(g)
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())#報錯
View Code

這裏,至少有兩個充分的理由說明 ,使用生成器比不使用生成器代碼更加清晰:

  1. 使用生成器以後,代碼行數更少。大家要記住,如果想把代碼寫的Pythonic,在保證代碼可讀性的前提下,代碼行數越少越好
  2. 不使用生成器的時候,對於每次結果,我們首先看到的是result.append(index),其次,才是index。也就是說,我們每次看到的是一個列表的append操作,只是append的是我們想要的結果。使用生成器的時候,直接yield index,少了列表append操作的幹擾,我們一眼就能夠看出,代碼是要返回index。

合理使用生成器,能夠有效提高代碼可讀性。只要大家完全接受了生成器的概念,理解了yield語句和return語句一樣,也是返回一個值。那麽,就能夠理解為什麽使用生成器比不使用生成器要好,能夠理解使用生成器真的可以讓代碼變得清晰易懂。生成器只(叠代)遍歷一次,如需要重復讀取,需要保存數據,再次獲取。

技術分享圖片
人口信息.txt文件內容
{name:北京,population:10}
{name:南京,population:100000}
{name:山東,population:10000}
{name:山西,population:19999}

def get_provice_population(filename):
    with open(filename) as f:
        for line in f:
            p=eval(line)   #提取出來的line都是字符串形式,需要使用eval提取起數據結構
            yield p[population]
gen=get_provice_population(人口信息.txt)

all_population=sum(gen)
for p in gen:
    print(p/all_population)
執行上面這段代碼,將不會有任何輸出,這是因為,生成器只能遍歷一次。在我們執行sum語句的時候,就遍歷了我們的生成器,當我們再次遍歷我們的生成器的時候,將不會有任何記錄。所以,上面的代碼不會有任何輸出。

因此,生成器的唯一註意事項就是:生成器只能遍歷一次。
eval 技術分享圖片
def test():
    for i in range(4):
        yield i

g=test()
g1=(i for i in g)
g2=(i for i in g1)
print(list(g1))
print(list(g2))
note1 技術分享圖片
def add(n,i):
    return n+i

def test():
    for i in range(4):
        yield i

g=test()
for n in [1,10]:
    g=(add(n,i) for i in g)
print(list(g))
note2 技術分享圖片
import os

def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs)
        next(g)
        return g
    return wrapper

@init
def list_files(target):
    while 1:
        dir_to_search=yield
        for top_dir,dir,files in os.walk(dir_to_search):
            for file in files:
                target.send(os.path.join(top_dir,file))
@init
def opener(target):
    while 1:
        file=yield
        fn=open(file)
        target.send((file,fn))
@init
def cat(target):
    while 1:
        file,fn=yield
        for line in fn:
            target.send((file,line))

@init
def grep(pattern,target):
    while 1:
        file,line=yield
        if pattern in line:
            target.send(file)
@init
def printer():
    while 1:
        file=yield
        if file:
            print(file)

g=list_files(opener(cat(grep(python,printer()))))

g.send(/test1)
協程應用:grep -rl /dir

【參考文檔】

python基礎之叠代器協議和生成器:https://www.cnblogs.com/luchuangao/p/6685626.html

叠代器協議和生成器:https://www.cnblogs.com/chenice/articles/6135714.html

第五篇、Python之叠代器與生成器