日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python迭代器和可迭代对象

發布時間:2023/12/31 python 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python迭代器和可迭代对象 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 迭代器 vs 可迭代對象

python中兩個迭代的概念,一個叫做迭代器(Iterator),一個叫做可迭代對象(Iterable),我們可以從collections模塊中導入

from collections.abc import Iterable,Iterator

當我們實現了迭代器之后,就可以使用for循環進行遍歷了。我們平常使用的字符串,列表,元組和字典等,底層都實現了迭代器。我們可以通過instance來判斷

from collections.abc import Iterable,Iterators = "abcdefgh" print(isinstance(s,Iterable)) # True print(isinstance(s,Iterator)) # Falsel = [1,2,3,4,5,6,7,8] print(isinstance(s,Iterable)) # True print(isinstance(s,Iterator)) # Falset = (1,2,3,4,5,6,7,8) print(isinstance(s,Iterable)) # True print(isinstance(s,Iterator)) # False

哈哈,上來就打臉了,發現字符串,列表和元組并不是迭代器,而是迭代對象。不要緊,繼續往下看

2. 如何實現迭代器

迭代器的實現非常簡單,只需要實現__iter__和__next__這兩個魔法函數即可,

  • 調用迭代器對象的 __iter__方法得到還是迭代器對象本身,就跟沒調用一樣
  • 調用迭代器對象的__next__方法返回下一個值,不依賴索引
  • 可以一直調用__next__直到取干凈,最后拋出異常StopIteration(停止迭代)
  • 我們來實現一個斐波那契數列

    from collections.abc import Iterable,Iteratorclass Fib:def __init__(self):self.prev = 0self.curr = 1def __iter__(self): # 自身就是迭代器,所以返回自身return selfdef __next__(self): # 只有實現了__next__函數才是迭代器print('run __next__ func')self.prev,self.curr = self.curr,self.curr+self.prevreturn self.currfib = Fib() print(isinstance(fib,Iterator)) # True print(isinstance(fib,Iterable)) # True # 通過next函數獲取下一個值 print(next(fib)) # 1 print(next(fib)) # 2 print(next(fib)) # 3 print(next(fib)) # 5

    索然無味啊,這迭代器好像并沒有什么特別的地方。每次調用next函數,就會進入到__next__函數中,然后計算curr的值,返回出來。

    上面實現的迭代器是沒有終止條件的,只要你愿意就可以一直計算下去,但是一般的迭代器都是有終止條件的,我們修改一下上面的迭代器

    from collections.abc import Iterable,Iterator from operator import indexclass Fib:def __init__(self, end):self.prev = 0self.curr = 1self.end = endself.index = 0def __iter__(self):return selfdef __next__(self):if self.index < self.end:self.prev,self.curr = self.curr,self.curr+self.prevself.index += 1return self.currelse:raise StopIterationfib = Fib(3)print(next(fib)) # 1 print(next(fib)) # 2 print(next(fib)) # 3

    可以看到正常輸出了,如果我們再次調用next呢?

    next(fib)

    Traceback (most recent call last):
    File “fib.py”, line 26, in
    print(next(fib)) # 5
    File “fib.py”, line 19, in next
    raise StopIteration
    StopIteration

    可以看到,我們得到一個異常對象,注意,主動拋出來的是異常對象,而不是異常,說明我們訪問結束了。for循環幫我們處理了異常

    for v in fib:print(v)

    for循環實現的邏輯是這樣的

    # create an iterator object from that iterable iter_obj = iter(iterable)# infinite loop while True:try:# get the next itemelement = next(iter_obj)print(element)# do something with elementexcept StopIteration:# if StopIteration is raised, break from loopbreak

    就很有意思,捕獲的是StopIteration,那如果我拋出的不是這個異常呢,比如我拋出一個IndexError

    import timeclass Fib:def __init__(self, end):self.prev = 0self.curr = 1self.end = endself.index = 0def __iter__(self):return selfdef __next__(self):if self.index < self.end:self.prev,self.curr = self.curr,self.curr+self.prevself.index += 1time.sleep(0.1)print('.',end='',flush=True)return self.currelse:raise IndexError('index > end')fib = Fib(10) for v in fib:print(v)

    Traceback (most recent call last):
    File “fib.py”, line 20, in
    for v in fib:
    File “fib.py”, line 17, in next
    raise IndexError(‘index > end’)
    IndexError: index > end

    可以看到for循環并沒有捕獲這個異常,而是直接拋出來了。后來我一尋思,覺得理應如此,for循環之需要判斷是否完成遍歷即可,其他的異常應該交給用戶自己處理。

    3. 迭代器的好處

    回想一下我們平時是怎么實現斐波那契數列的

    3.1 每次從頭計算(空間換時間)

    這種方式非常的耗時,每次都要重頭開始計算,但好處是想要獲取哪一位的數值直接調用即可,沒有任何約束和依賴

    import time def fib(end):i = 0prev, curr = 0, 1print('calc {}th '.format(end),end='')while i < end:prev, curr = curr, prev + curri += 1time.sleep(0.1)print('.',end='',flush=True)return curr start = time.perf_counter() for i in range(10):print(fib(i)) end = time.perf_counter() print('fib cost time {}'.format(end-start))

    3.2 結果保存下來(時間換空間)

    相對于從頭計算,我們可以將所有的結果保存下來,這種方式只需要計算一次,就可以獲取之前任意一次的結果,因為所有結果都保存下來了,所以很占空間。

    import time def fib(end):print('calc {} '.format(end),end='')res = [1]i = 0prev, curr = 0, 1while i < end:prev, curr = curr, prev + curri += 1res.append(curr)time.sleep(0.1)print('.',end='',flush=True)print('\n')return resstart = time.perf_counter() res = fib(10) for i in range(10):print(res[i]) end = time.perf_counter() print('fib cost time {}'.format(end-start))

    3.3 迭代器實現

    迭代器的實現前面已經說過了,很有意思的一點是,我們只有調用了next函數,迭代器才會返回我們下一個結果。如果我想要獲取第5個斐波那契數列值,我需要調用5次next函數,每調用一次就會執行一次__next__函數。

    import timeclass Fib:def __init__(self, end):self.prev = 0self.curr = 1self.end = endself.index = 0def __iter__(self):return selfdef __next__(self):print('calc',end='')if self.index < self.end:self.prev,self.curr = self.curr,self.curr+self.prevself.index += 1time.sleep(0.1)print('.',end='',flush=True)return self.currelse:raise StopIteration start = time.perf_counter() fib = Fib(10) for v in fib:print(v) end = time.perf_counter() print('fib cost time {}'.format(end-start))

    4 迭代器的局限

    迭代器不基于索引的方式獲取可迭代對象中的元素。節省了大量的內存,迭代器在內存中相當于只占一個數據的空間。因為每次取值都上一條數據會在內存釋放,加載當前的此條數據。惰性機制非常有用,我們平時處理大文件的時候,沒有辦法一下子讀到內存中來,就可以通過迭代器的方式一條一條的讀取。
    迭代器取值時不走回頭路,只能一直向下取值,所以不能直觀的查看里面的數據。老實講,我覺得索引的方式非常的好,想要獲取那個位置的數據,直接就可以獲取,歷史所有的狀態都保留了下來,而迭代器則需要一步一步計算過去。所以說,迭代器是一個雙刃劍,就看你想要在什么場景下使用

    可迭代對象

    可迭代對象就非常簡單了,迭代器需要實現兩個函數,一個是__next__用來計算下一個值,一個是__iter__返回自身,因為自身就是迭代器。可迭代對象只需要實現一個__iter__函數就可以了,這個函數返回一個迭代器。

    from collections.abc import Iterable,Iterator from operator import indexclass Fib:def __init__(self, end):self.prev = 0self.curr = 1self.end = endself.index = 0def __iter__(self):return selfdef __next__(self):if self.index < self.end:self.prev,self.curr = self.curr,self.curr+self.prevself.index += 1return self.currelse:raise StopIterationclass Fibable:def __init__(self, end):self.end = enddef __iter__(self):return Fib(self.end)fib = Fibable(10) print(isinstance(fib,Iterable)) # True 是可迭代對象 print(isinstance(fib,Iterator)) # False 不是迭代器 for i in fib:print(i)

    但是可迭代對象沒有實現__next__函數,因此無法通過next來獲取下一個元素,只能通過for循環獲取數據,如果調用next函數則會報錯,而且可以看到,報的是類型錯誤,next函數只能接受迭代器。

    next(fib)

    Traceback (most recent call last):
    File “fib.py”, line 38, in
    next(fib)
    TypeError: ‘Fibable’ object is not an iterator

    自定義可迭代數據

    python中還提供了一個魔法函數__getitem__,實現這個函數之后,就可以使用for循環遍歷了,pytorch中的dataloader就是這種方式,先舉個簡單的例子。

    from collections.abc import Iterable,Iteratorclass Fib:def __init__(self) -> None:self.res = [0,1,1,2,3,5,8,13,21,35,56,91]def __getitem__(self,index):print('run fib func...')return self.res[index]fib = Fib() print(isinstance(fib,Iterable)) # False 不是可迭代對象 print(isinstance(fib,Iterator)) # False 不是迭代器 for v in fib: # 啥也不是,但就是可以for循環print(v)

    Fib既不是可迭代對象,也不是迭代器,但是可以使用for循環來遍歷數據。而且發現__getitem__還有一個入參index,這說明我們其實可以直接通過索引遍歷數據。看看斐波那契數列如何實現

    from collections.abc import Iterable,Iteratorclass Fib:def __init__(self) -> None:passdef __getitem__(self,index):i = 0prev, curr = 0, 1while i < index:print('calc fib...')prev, curr = curr, prev + curri += 1return currfib = Fib() print(isinstance(fib,Iterable)) # False 不是可迭代對象 print(isinstance(fib,Iterator)) # False 不是迭代器print(fib[3])

    上面實現了斐波那契數列,我們沒有保存結果,每次讀取結果,每次重頭開始計算。簡直是無話可說,這就相當于第一種最耗時的方案,非常的愚蠢。我可以稍作修改,變成第二種方式,把所有的結果再初始化的時候都計算一遍然后保存下來,而且可以隨意索引,就是占空間。

    from collections.abc import Iterable,Iteratorclass Fib:def __init__(self, end):# 初始化的時候將所有的結果計算一遍,然后保存下來self.res = []i = 0prev, curr = 0, 1while i < end:prev, curr = curr, prev + curri += 1self.res.append(curr)def __getitem__(self,index):return self.res[index]fib = Fib(10) print(isinstance(fib,Iterable)) # False 不是可迭代對象 print(isinstance(fib,Iterator)) # False 不是迭代器# 可以使用for循環 for v in fib:print(v)print(fib[3]) # 可以通過索引隨意取值

    其實我覺得這就是一個單純的get方法而已,因為其實還有一個方法,叫做__setitem__,這不就是平時使用的@pro和@name.setter嗎?
    python提供了iter函數,可以將__getitem__轉變稱迭代器

    fib = Fib(10) print(isinstance(fib,Iterable)) # False 不是可迭代對象 print(isinstance(fib,Iterator)) # False 不是迭代器fib_iter = iter(fib) print(isinstance(fib_iter,Iterable)) # True 是可迭代對象 print(isinstance(fib_iter,Iterator)) # True 是迭代器

    胡亂測試

    可迭代對象只實現了__iter__,如果我再實現了__next__會不會變成迭代器

    from collections.abc import Iterable,Iterator from operator import indexclass Fib:def __init__(self, end):self.prev = 0self.curr = 1self.end = endself.index = 0def __iter__(self): # 本身就是迭代器,直接返回自己return selfdef __next__(self):if self.index < self.end:self.prev,self.curr = self.curr,self.curr+self.prevself.index += 1return self.currelse:raise StopIterationclass Fibable:def __init__(self, end):self.end = enddef __iter__(self): # 要求返回一個迭代器return Fib(self.end)def __next__(self):print('next func ...')fib = Fibable(10) print(isinstance(fib,Iterable)) # True 可迭代對象 print(isinstance(fib,Iterator)) # True 不是迭代器 for i in fib:print(i)

    萬萬沒想到,竟然真的變成迭代器了,而且可以正常打印出結果。這說明,在迭代的過程中并沒有調用__next__,而是調用了__iter__函數。這說明如果同時存在的話,會優先調用__iter__。再嘗試使用next執行看看

    next(fib) # next func ... next(fib) # next func ... next(fib) # next func ...

    輸出的是__next__的結果,而不是__iter__的結果。

    如果同時存在__getitem__和__next__會怎么樣呢?

    from collections.abc import Iterable,Iterator from operator import indexclass Fib:def __init__(self):passdef __iter__(self):return selfdef __next__(self):print('next func')def __getitem__(self,index):print('getitem func {}'.format(index))fib = Fib() print(isinstance(fib,Iterable)) # True 是可迭代對象 print(isinstance(fib,Iterator)) # True 是迭代器 i = 0 for v in fib:if i > 10:breaki += 1next(fib) next(fib) next(fib)fib[3]

    for會發現優先調用__next__函數,使用next函數調用__next__函數,使用索引調用__getitem__函數

    總結

    • 迭代器:實現__iter__和__next__兩個魔法函數,可以使用for循環和next
    • 可迭代對象:只能實現__iter__函數,并且這個函數返回的是一個迭代器,可以使用for,由于沒有實現__next__函數,所以不能使用next
    • 自定義可迭代數據:實現__getitem__函數,有一個入參index,意味著我們可以通過索引訪問,所以也就意味著需要保存較多的數據在內存中,通過iter函數可以將自定義的迭代數據轉換成迭代器

    個人的一些見解
    老實講,我覺得迭代器還是比較雞肋的,為了計算下一個狀態,需要保存上下文,然后通過處理得到新的狀態。如果我們想要實現的就是在循環中不斷往下迭代,不需要之前的狀態,那么可以使用迭代器,如果想要通過索引來快速得到想要的結果,最好還是使用自定義的迭代對象。

    應用舉例

    我能想到使用迭代器的場景,一般會滿足兩個條件

  • 數據集非常的大,無法一下子加載到內存中
  • 順序的消耗數據,一般不會有狀態的跳躍
  • 例如我們想要處理1T的數據,不會直接加載到內存中,而是一條一條的加載,這個時候我們就可以把所有的路徑加載進來,然后讀取每個路徑的文件,迭代的對這些數據進行處理。

    總結

    以上是生活随笔為你收集整理的python迭代器和可迭代对象的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。