看到類似__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沒什么區別,這完全歸功於動態語言的“鴨子類型”,不需要強制繼承某個接口。