Python 迭代器 & __iter__方法


看到類似__slots__這種形如__xxx__的變量或者函數名就要注意,這些在Python中是有特殊用途的。

__iter__

如果一個類想被用於for ... in循環,類似list或tuple那樣,就必須實現一個__iter__()方法,該方法返回一個迭代對象,然后,Python的for循環就會不斷調用該迭代對象的next()方法拿到循環的下一個值,直到遇到StopIteration錯誤時退出循環。

迭代器就是重復地做一些事情,可以簡單的理解為循環,在python中實現了__iter__方法的對象是可迭代的,實現了next()方法的對象是迭代器,這樣說起來有點拗口,實際上要想讓一個迭代器工作,至少要實現__iter__方法和next方法。很多時候使用迭代器完成的工作使用列表也可以完成,但是如果有很多值列表就會占用太多的內存,而且使用迭代器也讓我們的程序更加通用、優雅、pythonic。

我們以斐波那契數列為例,寫一個Fib類,可以作用於for循環:

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化兩個計數器a,b
    def __iter__(self):
        return self # 實例本身就是迭代對象,故返回自己
    def next(self):
        self.a, self.b = self.b, self.a + self.b # 計算下一個值
        if self.a > 100000: # 退出循環的條件
            raise StopIteration();
        return self.a # 返回下一個值
for n in Fib():
...     print n
...
1
1
2
3
5
...

__getitem__

Fib實例雖然能作用於for循環,看起來和list有點像,但是,把它當成list來使用還是不行,比如,取第5個元素:

Fib()[5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing

要表現得像list那樣按照下標取出元素,需要實現__getitem__()方法:

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a



>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]

但是list有個神奇的切片方法:

>>> range(100)[5:10]
[5, 6, 7, 8, 9]

#對於Fib卻報錯。原因是__getitem__()傳入的參數可能是一個int,也可能是一個切片對象slice,所以要做判斷:

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int):
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice):
            start = n.start
            stop = n.stop
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L

#現在試試Fib的切片:

>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]


#但是沒有對step參數作處理:

>>> f[:10:2]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

也沒有對負數作處理,所以,要正確實現一個__getitem__()還是有很多工作要做的。

 

此外,如果把對象看成dict,__getitem__()的參數也可能是一個可以作key的object,例如str。

與之對應的是__setitem__()方法,把對象視作list或dict來對集合賦值。最后,還有一個__delitem__()方法,用於刪除某個元素。

總之,通過上面的方法,我們自己定義的類表現得和Python自帶的list、tuple、dict沒什么區別,這完全歸功於動態語言的“鴨子類型”,不需要強制繼承某個接口。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM