Python—迭代器和生成器

来源:https://www.cnblogs.com/Golanguage/archive/2020/02/15/12315181.html
-Advertisement-
Play Games

楔子 假如我現在有一個列表l=['a','b','c','d','e'],我想取列表中的內容,有幾種方式? 首先,我可以通過索引取值l[0],其次我們是不是還可以用for迴圈來取值呀? 你有沒有仔細思考過,用索引取值和for迴圈取值是有著微妙區別的。 如果用索引取值,你可以取到任意位置的值,前提是你 ...


楔子

假如我現在有一個列表l=['a','b','c','d','e'],我想取列表中的內容,有幾種方式?

首先,我可以通過索引取值l[0],其次我們是不是還可以用for迴圈來取值呀?

你有沒有仔細思考過,用索引取值和for迴圈取值是有著微妙區別的。

如果用索引取值,你可以取到任意位置的值,前提是你要知道這個值在什麼位置。

如果用for迴圈來取值,我們把每一個值都取到,不需要關心每一個值的位置,因為只能順序的取值,並不能跳過任何一個直接去取其他位置的值。

但你有沒有想過,我們為什麼可以使用for迴圈來取值?

for迴圈內部是怎麼工作的呢?

迭代器

python中的for迴圈

要瞭解python中的for迴圈是怎麼回事兒,咱們還是要從代碼的角度出發。

首先,我們對一個列表進行for迴圈。

for i in [1,2,3,4]:  
    print(i)

上面這段代碼肯定是沒有問題的,但是我們換一種情況,來迴圈一個數字1234試試

for i in 1234
    print(i) 

結果:
Traceback (most recent call last):
  File "test.py", line 4, in <module>
    for i in 1234:
TypeError: 'int' object is not iterable

看,報錯了!報了什麼錯呢?“TypeError: 'int' object is not iterable”,說int類型不是一個iterable,那這個iterable是個啥?

假如你不知道什麼是iterable,我們可以翻翻詞典,首先得到一個中文的解釋,儘管翻譯過來了你可能也不知道,但是沒關係,我會帶著你一步一步來分析。

迭代和可迭代協議

什麼叫迭代

現在,我們已經獲得了一個新線索,有一個叫做“可迭代的”概念

首先,我們從報錯來分析,好像之所以1234不可以for迴圈,是因為它不可迭代。那麼如果“可迭代”,就應該可以被for迴圈了。

這個我們知道呀,字元串、列表、元組、字典、集合都可以被for迴圈,說明他們都是可迭代的

我們怎麼來證明這一點呢?

from collections import Iterable
                             
l = [1,2,3,4]                
t = (1,2,3,4)                
d = {1:2,3:4}                
s = {1,2,3,4}                
                             
print(isinstance(l,Iterable))
print(isinstance(t,Iterable))
print(isinstance(d,Iterable))
print(isinstance(s,Iterable))

結合我們使用for迴圈取值的現象,再從字面上理解一下,其實迭代就是我們剛剛說的,可以將某個數據集內的數據“一個挨著一個的取出來”,就叫做迭代

可迭代協議

我們現在是從結果分析原因,能被for迴圈的就是“可迭代的”,但是如果正著想,for怎麼知道誰是可迭代的呢?

假如我們自己寫了一個數據類型,希望這個數據類型里的東西也可以使用for被一個一個的取出來,那我們就必須滿足for的要求。這個要求就叫做“協議”。

可以被迭代要滿足的要求就叫做可迭代協議。可迭代協議的定義非常簡單,就是內部實現了__iter__方法。

接下來我們就來驗證一下:

print(dir([1,2]))
print(dir((2,3)))
print(dir({1:2}))
print(dir({1,2}))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
結果

總結一下我們現在所知道的:可以被for迴圈的都是可迭代的,要想可迭代,內部必須有一個__iter__方法。

接著分析,__iter__方法做了什麼事情呢?

print([1,2].__iter__())

結果
<list_iterator object at 0x1024784a8>

執行了list([1,2])的__iter__方法,我們好像得到了一個list_iterator,現在我們又得到了一個新名詞——iterator。

iterator,這裡給我們標出來了,是一個電腦中的專屬名詞,叫做迭代器。

迭代器協議

既什麼叫“可迭代”之後,又一個歷史新難題,什麼叫“迭代器”?

雖然我們不知道什麼叫迭代器,但是我們現在已經有一個迭代器了,這個迭代器是一個列表的迭代器。

我們來看看這個列表的迭代器比起列表來說實現了哪些新方法,這樣就能揭開迭代器的神秘面紗了吧?

複製代碼
'''
dir([1,2].__iter__())是列表迭代器中實現的所有方法,dir([1,2])是列表中實現的所有方法,都是以列表的形式返回給我們的,為了看的更清楚,我們分別把他們轉換成集合,
然後取差集。 ''' #print(dir([1,2].__iter__())) #print(dir([1,2])) print(set(dir([1,2].__iter__()))-set(dir([1,2]))) 結果: {'__length_hint__', '__next__', '__setstate__'}
複製代碼

我們看到在列表迭代器中多了三個方法,那麼這三個方法都分別做了什麼事呢?

複製代碼
iter_l = [1,2,3,4,5,6].__iter__()
#獲取迭代器中元素的長度
print(iter_l.__length_hint__())
#根據索引值指定從哪裡開始迭代
print('*',iter_l.__setstate__(4))
#一個一個的取值
print('**',iter_l.__next__())
print('***',iter_l.__next__())
複製代碼

這三個方法中,能讓我們一個一個取值的神奇方法是誰?

沒錯!就是__next__

在for迴圈中,就是在內部調用了__next__方法才能取到一個一個的值。

那接下來我們就用迭代器的next方法來寫一個不依賴for的遍歷。

複製代碼
l = [1,2,3,4]
l_iter = l.__iter__()
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
複製代碼

這是一段會報錯的代碼,如果我們一直取next取到迭代器里已經沒有元素了,就會拋出一個異常StopIteration,告訴我們,列表中已經沒有有效的元素了。

這個時候,我們就要使用異常處理機制來把這個異常處理掉。

複製代碼
l = [1,2,3,4]
l_iter = l.__iter__()
while True:
    try:
        item = l_iter.__next__()
        print(item)
    except StopIteration:
        break
複製代碼

那現在我們就使用while迴圈實現了原本for迴圈做的事情,我們是從誰那兒獲取一個一個的值呀?是不是就是l_iter?好了,這個l_iter就是一個迭代器。

迭代器遵循迭代器協議:必須擁有__iter__方法和__next__方法。

還賬:next和iter方法

如此一來,關於迭代器和生成器的方法我們就還清了兩個,最後我們來看看range()是個啥。首先,它肯定是一個可迭代的對象,但是它是否是一個迭代器?我們來測試一下

print('__next__' in dir(range(12)))  #查看'__next__'是不是在range()方法執行之後內部是否有__next__
print('__iter__' in dir(range(12)))  #查看'__next__'是不是在range()方法執行之後內部是否有__next__

from collections import Iterator
print(isinstance(range(100000000),Iterator))  #驗證range執行之後得到的結果不是一個迭代器

# range的返回值是一個可迭代對象

為什麼要有for迴圈

基於上面講的列表這一大堆遍歷方式,聰明的你立馬看除了端倪,於是你不知死活大聲喊道,你這不逗我玩呢麽,有了下標的訪問方式,我可以這樣遍歷一個列表啊

l=[1,2,3]

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

#要毛線for迴圈,要毛線可迭代,要毛線迭代器

沒錯,序列類型字元串,列表,元組都有下標,你用上述的方式訪問,perfect!但是你可曾想過非序列類型像字典,集合,文件對象的感受,所以嘛,年輕人,for迴圈就是基於迭代器協議提供了一個統一的可以遍歷所有對象的方法,即在遍歷之前,先調用對象的__iter__方法將其轉換成一個迭代器,然後使用迭代器協議去實現迴圈訪問,這樣所有的對象就都可以通過for迴圈來遍歷了,而且你看到的效果也確實如此,這就是無所不能的for迴圈,覺悟吧,年輕人

生成器

初識生成器

我們知道的迭代器有兩種:一種是調用方法直接返回的,一種是可迭代對象通過執行iter方法得到的,迭代器有的好處是可以節省記憶體。

如果在某些情況下,我們也需要節省記憶體,就只能自己寫。我們自己寫的這個能實現迭代器功能的東西就叫生成器。

Python中提供的生成器:

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

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

生成器Generator:

  本質:迭代器(所以自帶了__iter__方法和__next__方法,不需要我們去實現)

  特點:惰性運算,開發者自定義

生成器函數

一個包含yield關鍵字的函數就是一個生成器函數。yield可以為我們從函數中返回值,但是yield又不同於return,return的執行意味著程式的結束,調用生成器函數不會得到返回的具體的值,而是得到一個可迭代的對象。每一次獲取這個可迭代對象的值,就能推動函數的執行,獲取新的返回值。直到函數執行結束。

# 初識生成器一
import time def genrator_fun1(): a = 1 print('現在定義了a變數') yield a b = 2 print('現在又定義了b變數') yield b g1 = genrator_fun1() print('g1 : ',g1) #列印g1可以發現g1就是一個生成器 print('-'*20) #我是華麗的分割線 print(next(g1)) time.sleep(1) #sleep一秒看清執行過程 print(next(g1))

生成器有什麼好處呢?就是不會一下子在記憶體中生成太多數據

假如我想讓工廠給學生做校服,生產2000000件衣服,我和工廠一說,工廠應該是先答應下來,然後再去生產,我可以一件一件的要,也可以根據學生一批一批的找工廠拿。
而不能是一說要生產2000000件衣服,工廠就先去做生產2000000件衣服,等回來做好了,學生都畢業了。。。

#初識生成器二

def produce():
    """生產衣服"""
    for i in range(2000000):
        yield "生產了第%s件衣服"%i

product_g = produce()
print(product_g.__next__()) #要一件衣服
print(product_g.__next__()) #再要一件衣服
print(product_g.__next__()) #再要一件衣服
num = 0
for i in product_g:         #要一批衣服,比如5件
    print(i)
    num +=1
    if num == 5:
        break

#到這裡我們找工廠拿了8件衣服,我一共讓我的生產函數(也就是produce生成器函數)生產2000000件衣服。
#剩下的還有很多衣服,我們可以一直拿,也可以放著等想拿的時候再拿

更多應用

生成器監聽文件輸入的例子

import time

def tail(filename):
    f = open(filename)
    f.seek(0, 2) #從文件末尾算起
    while True:
        line = f.readline()  # 讀取文件中新的文本行
        if not line:
            time.sleep(0.1)
            continue
        yield line

tail_g = tail('tmp')
for line in tail_g:
    print(line)

send

def generator():
    print(123)
    content = yield 1
    print('=======',content)
    print(456)
    yield2

g = generator()
ret = g.__next__()
print('***',ret)
ret = g.send('hello')   #send的效果和next一樣
print('***',ret)

#send 獲取下一個值的效果和next基本一致
#只是在獲取下一個值的時候,給上一yield的位置傳遞一個數據
#使用send的註意事項
    # 第一次使用生成器的時候 是用next獲取下一個值
    # 最後一個yield不能接受外部的值
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total/count


g_avg = averager()
next(g_avg)
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))
計算移動平均值(1) 計算移動平均值(2)_預激協程的裝飾器

yield from

def gen1():
    for c in 'AB':
        yield c
    for i in range(3):
        yield i

print(list(gen1()))

def gen2():
    yield from 'AB'
    yield from range(3)

print(list(gen2()))

列表推導式和生成器表達式

#老男孩由於峰哥的強勢加盟很快走上了上市之路,alex思來想去決定下幾個雞蛋來報答峰哥

egg_list=['雞蛋%s' %i for i in range(10)] #列表解析

#峰哥瞅著alex下的一筐雞蛋,捂住了鼻子,說了句:哥,你還是給我只母雞吧,我自己回家下

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

總結:

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

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

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

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

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

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

 

更多精彩請見——迭代器生成器專題:http://www.cnblogs.com/Eva-J/articles/7276796.html

本章小結

可迭代對象:

  擁有__iter__方法

  特點:惰性運算

  例如:range(),str,list,tuple,dict,set

迭代器Iterator:

  擁有__iter__方法和__next__方法

  例如:iter(range()),iter(str),iter(list),iter(tuple),iter(dict),iter(set),reversed(list_o),map(func,list_o),filter(func,list_o),file_o

生成器Generator:

  本質:迭代器,所以擁有__iter__方法和__next__方法

  特點:惰性運算,開發者自定義

使用生成器的優點:

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

#列表解析
sum([i for i in range(100000000)])#記憶體占用大,機器容易卡死
 
#生成器表達式
sum(i for i in range(100000000))#幾乎不占記憶體

2.提高代碼可讀性

生成器相關的面試題

生成器在編程中發生了很多的作用,善用生成器可以幫助我們解決很多複雜的問題

除此之外,生成器也是面試題中的重點,在完成一些功能之外,人們也想出了很多魔性的面試題。
接下來我們就來看一看~

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

g=demo()

g1=(i for i in g)
g2=(i for i in g1)

print(list(g1))
print(list(g2))
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))
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

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 什麼是模塊? 常見的場景:一個模塊就是一個包含了python定義和聲明的文件,文件名就是模塊名字加上.py的尾碼。 但其實import載入的模塊分為四個通用類別: 1 使用python編寫的代碼(.py文件) 2 已被編譯為共用庫或DLL的C或C++擴展 3 包好一組模塊的包 4 使用C編寫並鏈接到 ...
  • https://www.cnblogs.com/WUXIAOCHANG/p/10886534.html https://blog.csdn.net/pengjwhx/article/details/84867112 ...
  • 楔子 在講今天的內容之前,我們先來講一個故事,講的什麼呢?從前有座山,山裡有座廟,廟裡有個老和尚講故事,講的什麼呢?從前有座山,山裡有座廟,廟裡有個老和尚講故事,講的什麼呢?從前有座山,山裡有座廟,廟裡有個老和尚講故事,講的什麼呢?從前有座山,山裡有座廟,廟裡有個老和尚講故事,講的什麼呢...... ...
  • 楔子 在講新知識之前,我們先來複習複習函數的基礎知識。 問:函數怎麼調用? 函數名() 如果你們這麼說。。。那你們就對了!好了記住這個事兒別給忘記了,咱們繼續談下一話題。。。 來你們在自己的環境里列印一下自己的名字。 你們是怎麼打的呀? 是不是print('xxx'),好了,現在你們結合我剛剛說的函 ...
  • 第一個Django程式 從本章節開始將通過實現一個投票應用程式,來讓用戶逐步的瞭解Django。這個程式由兩步分組成: 公共站點,允許用戶訪問進行投票,和查看投票。 站點管理,允許添加,刪除,修改投票信息。 1、創建項目 本文繼承前一篇章節的環境(centos 7 python3.6.2 Djang ...
  • 註:若沒有特指是 靜態成員時,預設都是普通成員; 1 類中的普通成員 類中的成員變數 和 成員函數 是分開存儲的。其中, 1)每個對象都有獨立的成員變數;成員變數可以存儲在 棧空間、堆空間、全局數據區; 2)所有對象共用類的成員函數;成員函數 只能存儲在 代碼段; 2 類中的靜態成員(static) ...
  • 問題描述: Pycharm創建Django項目提示:HTTPSConnectionPool(host='files.pythonhosted.org', port=443): Read timed out 解決方法: 這個問題是因為訪問files.pythonhosted.org超時引起的,換為國內 ...
  • javaSE學習筆記(15) 緩衝流、轉換流、序列化流 緩衝流 昨天複習了基本的一些流,作為IO流的入門,今天我們要見識一些更強大的流。比如能夠高效讀寫的緩衝流,能夠轉換編碼的轉換流,能夠持久化存儲對象的序列化流等等。這些功能更為強大的流,都是在基本的流對象基礎之上創建而來的,相當於是對基本流對象的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...