Python迭代器

EisenJi發表於2022-07-09

最近在看Python基礎教程(第三版),是之前python課的課本,但是之前沒讀過,雖然python一直用得挺多,但重新讀讀收穫還挺大。這裡做個筆記。
先是迭代器是什麼並簡單實現一個迭代器,然後是實現了一些range()。後面本來想寫生成器和八皇后問題,但是發現了一些很不錯的部落格,比我能寫出來的好多了,把連結收藏在後面了。


​ 迭代器是像迴圈一樣重複很多次,但不會像列表那樣一次性全部生成,而是需要用的時候再生成,就節省了記憶體資源。有時可能只想一個個地獲取值,而不是用列表一次性獲取,列表可能佔用太多記憶體,並且有時沒辦法使用列表,列表的長度會到無窮大。


1、根據基本定義實現一個迭代器類

簡單的說,迭代器類中:

  • 定義__iter__和__next__兩個方法
  • __iter__返回它自己,__next__返回下一個,沒有可返回的就丟擲StopIteration異常。
  • 迭代器物件Iterator就是字面意思,可以被迭代,比如可以呼叫next方法(一個迭代器物件it,next(it)就是it.__next__() ),比如可以放進for迴圈(for x in it)

根據上面描述可以簡單寫一個迭代器出來

class TestIterator:
    value = 0

    def __next__(self):  
        self.value += 1
        if self.value > 10:  # 到這個條件了沒有可返回的了就丟擲一個StopIteration異常
            raise StopIteration
        return self.value  # 每次返回下一個
    
    def __iter__(self):  # 這裡返回它自己
        return self


if __name__ == '__main__':
    ti = TestIterator()
    print(list(ti))

執行的結果是:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


2、稍微具體一點的__iter__和__next__

嗯於是就能知道了,關於__iter__和__next__:

實現了方法__iter__的物件是可迭代的,而實現了方法__next__的物件是迭代器。

方法__iter__返回一個迭代器,它是包含方法__next__的物件。

文件中也寫得很清楚了,呼叫__next__時,迭代器應返回其下一個值。如果迭代器沒有可供返回的值,則引發StopIteration異常。

iter返回一個迭代器物件,即object.__iter__()。如果指定了sentinel(哨兵),這個迭代器將不斷呼叫直到返回的是sentinel。


3、for迴圈的原理,斐波那契數列的例子

然後再來一個斐波那契數列的例子,迭代器物件可以被放進for迴圈。

class Fibs:
    def __init__(self) -> None:
        self.a = 0
        self.b = 1

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        return self.a
    
    def __iter__(self):
        return self


if __name__ == '__main__':
    fibs = Fibs()
    for f in fibs:  # 首先會執行__iter__方法獲取返回值,就它自己,然後執行一次它的__next__方法,不斷迴圈。
        if f > 1000:
            print(f)
            break

返回這個數列中第一個大於1000的數,是1597。


4、關於range()

(base) eisen@pop-os:~$ python3.8
Python 3.8.10 (default, Mar 15 2022, 12:22:08) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> v1 = range(20)
>>> dir(v1)
['__bool__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index', 'start', 'step', 'stop']

這裡先看到v1裡面只有__iter__,v1為可迭代物件Iterable,讓v2 = v1.__iter__()

>>> v2 = v1.__iter__()
>>> dir(v2)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__']

可以看到v2既有__iter__又有__next__為迭代器物件。range就是執行v1的__iter__獲取到裡面的迭代器物件v2,再執行v2的__next__方法...

>>> v2.__next__()
0
>>> v2.__next__()
1
>>> v2.__next__()
2
>>> v2.__next__()
3
>>> v2.__next__()
4
>>> v2.__next__()
5

到這裡就能理解書上這段話了。

於是實現一下range就很簡單了,在實現的MyRange裡面,__iter__方法就生成一個IterRange類的迭代器物件。

class IterRange():
    def __init__(self, num):
        self.num = num
        self.counter = -1

    def __iter__(self):
        return self

    def __next__(self):
        self.counter += 1
        if self.counter == self.num:
            raise StopIteration()
        return self.counter


class MyRange():
    def __init__(self, num):
        self.num = num

    def __iter__(self):
        return IterRange(self.num)


v1 = MyRange(20)
for i in v1:
    print(i)

輸出就是把0到19列印出來。


本來還想寫後面的生成器和八皇后問題,但是發現了一些很不錯的部落格,我就沒必要自己寫了。這裡收藏一下。

Python中生成器的原理,這一篇講了生成器的使用方法,和詳細的原理(裡面有原始碼)

深入理解Python中的生成器,這一篇寫了生成器的語法,以及它支援的方法close/send等。

python生成器和迭代器有這篇就夠了,這篇寫得很詳細,後面還有補充itertools庫的學習。

還記得八皇后的解法嗎,這篇講了個故事。

相關文章