在 Python 中,咱們能夠常常看到以雙下劃線 __
包裹起來的方法,好比最多見的 __init__
,這些方法被稱爲魔法方法(magic method)或特殊方法(special method)。簡單地說,這些方法能夠給 Python 的類提供特殊功能,方便咱們定製一個類,好比 __init__
方法能夠對實例屬性進行初始化。html
完整的特殊方法列表可在這裏查看,本文介紹部分經常使用的特殊方法:python
__new__
__str__
, __repr__
__iter__
__getitem__
, __setitem__
, __delitem__
__getattr__
, __setattr__
, __delattr__
__call__
在 Python 中,當咱們建立一個類的實例時,類會先調用 __new__(cls[, ...])
來建立實例,而後 __init__
方法再對該實例(self)進行初始化。app
關於 __new__
和 __init__
有幾點須要注意:函數
__new__
是在 __init__
以前被調用的;__new__
是類方法,__init__
是實例方法;__new__
方法,須要返回類的實例;通常狀況下,咱們不須要重載 __new__
方法。但在某些狀況下,咱們想控制實例的建立過程,這時能夠經過重載 __new_
方法來實現。spa
讓咱們看一個例子:code
class A(object): _dict = dict() def __new__(cls): if 'key' in A._dict: print "EXISTS" return A._dict['key'] else: print "NEW" return object.__new__(cls) def __init__(self): print "INIT" A._dict['key'] = self
在上面,咱們定義了一個類 A
,並重載了 __new__
方法:當 key
在 A._dict
中時,直接返回 A._dict['key']
,不然建立實例。htm
執行狀況:對象
>>> a1 = A() NEW INIT >>> a2 = A() EXISTS INIT >>> a3 = A() EXISTS INIT
先看一個簡單的例子:ip
class Foo(object): def __init__(self, name): self.name = name >>> print Foo('ethan') <__main__.Foo object at 0x10c37aa50>
在上面,咱們使用 print 打印一個實例對象,但若是咱們想打印更多信息呢,好比把 name 也打印出來,這時,咱們能夠在類中加入 __str__
方法,以下:ci
class Foo(object): def __init__(self, name): self.name = name def __str__(self): return 'Foo object (name: %s)' % self.name >>> print Foo('ethan') # 使用 print Foo object (name: ethan) >>> >>> str(Foo('ethan')) # 使用 str 'Foo object (name: ethan)' >>> >>> Foo('ethan') # 直接顯示 <__main__.Foo at 0x10c37a490>
能夠看到,使用 print 和 str 輸出的是 __str__
方法返回的內容,但若是直接顯示則不是,那能不能修改它的輸出呢?固然能夠,咱們只需在類中加入 __repr__
方法,好比:
class Foo(object): def __init__(self, name): self.name = name def __str__(self): return 'Foo object (name: %s)' % self.name def __repr__(self): return 'Foo object (name: %s)' % self.name >>> Foo('ethan') 'Foo object (name: ethan)'
能夠看到,如今直接使用 Foo('ethan')
也能夠顯示咱們想要的結果了,然而,咱們發現上面的代碼中,__str__
和 __repr__
方法的代碼是同樣的,能不能精簡一點呢,固然能夠,以下:
class Foo(object): def __init__(self, name): self.name = name def __str__(self): return 'Foo object (name: %s)' % self.name __repr__ = __str__
在某些狀況下,咱們但願實例對象可被用於 for...in
循環,這時咱們須要在類中定義 __iter__
和 next
(在 Python3 中是 __next__
)方法,其中,__iter__
返回一個迭代對象,next
返回容器的下一個元素,在沒有後續元素時拋出 StopIteration
異常。
看一個斐波那契數列的例子:
class Fib(object): def __init__(self): self.a, self.b = 0, 1 def __iter__(self): # 返回迭代器對象自己 return self def next(self): # 返回容器下一個元素 self.a, self.b = self.b, self.a + self.b return self.a >>> fib = Fib() >>> for i in fib: ... if i > 10: ... break ... print i ... 1 1 2 3 5 8
有時,咱們但願可使用 obj[n]
這種方式對實例對象進行取值,好比對斐波那契數列,咱們但願能夠取出其中的某一項,這時咱們須要在類中實現 __getitem__
方法,好比下面的例子:
class Fib(object): def __getitem__(self, n): a, b = 1, 1 for x in xrange(n): a, b = b, a + b return a >>> fib = Fib() >>> fib[0], fib[1], fib[2], fib[3], fib[4], fib[5] (1, 1, 2, 3, 5, 8)
咱們還想更進一步,但願支持 obj[1:3]
這種切片方法來取值,這時 __getitem__
方法傳入的參數多是一個整數,也多是一個切片對象 slice,所以,咱們須要對傳入的參數進行判斷,可使用 isinstance
進行判斷,改後的代碼以下:
class Fib(object): def __getitem__(self, n): if isinstance(n, slice): # 若是 n 是 slice 對象 a, b = 1, 1 start, stop = n.start, n.stop L = [] for i in xrange(stop): if i >= start: L.append(a) a, b = b, a + b return L if isinstance(n, int): # 若是 n 是 int 型 a, b = 1, 1 for i in xrange(n): a, b = b, a + b return a
如今,咱們試試用切片方法:
>>> fib = Fib() >>> fib[0:3] [1, 1, 2] >>> fib[2:6] [2, 3, 5, 8]
上面,咱們只是簡單地演示了 getitem 的操做,可是它還很不完善,好比沒有對負數處理,不支持帶 step 參數的切片操做 obj[1:2:5]
等等,讀者有興趣的話能夠本身實現看看。
__geitem__
用於獲取值,相似地,__setitem__
用於設置值,__delitem__
用於刪除值,讓咱們看下面一個例子:
class Point(object): def __init__(self): self.coordinate = {} def __str__(self): return "point(%s)" % self.coordinate def __getitem__(self, key): return self.coordinate.get(key) def __setitem__(self, key, value): self.coordinate[key] = value def __delitem__(self, key): del self.coordinate[key] print 'delete %s' % key def __len__(self): return len(self.coordinate) __repr__ = __str__
在上面,咱們定義了一個 Point 類,它有一個屬性 coordinate(座標),是一個字典,讓咱們看看使用:
>>> p = Point() >>> p['x'] = 2 # 對應於 p.__setitem__('x', 2) >>> p['y'] = 5 # 對應於 p.__setitem__('y', 5) >>> p # 對應於 __repr__ point({'y': 5, 'x': 2}) >>> len(p) # 對應於 p.__len__ 2 >>> p['x'] # 對應於 p.__getitem__('x') 2 >>> p['y'] # 對應於 p.__getitem__('y') 5 >>> del p['x'] # 對應於 p.__delitem__('x') delete x >>> p point({'y': 5}) >>> len(p) 1
當咱們獲取對象的某個屬性,若是該屬性不存在,會拋出 AttributeError 異常,好比:
class Point(object): def __init__(self, x=0, y=0): self.x = x self.y = y >>> p = Point(3, 4) >>> p.x, p.y (3, 4) >>> p.z --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-547-6dce4e43e15c> in <module>() ----> 1 p.z AttributeError: 'Point' object has no attribute 'z'
那有沒有辦法不讓它拋出異常呢?固然有,只需在類的定義中加入 __getattr__
方法,好比:
class Point(object): def __init__(self, x=0, y=0): self.x = x self.y = y def __getattr__(self, attr): if attr == 'z': return 0 >>> p = Point(3, 4) >>> p.z 0
如今,當咱們調用不存在的屬性(好比 z)時,解釋器就會試圖調用 __getattr__(self, 'z')
來獲取值,可是,上面的實現還有一個問題,當咱們調用其餘屬性,好比 w ,會返回 None,由於 __getattr__
默認返回就是 None,只有當 attr 等於 'z' 時才返回 0,若是咱們想讓 __getattr__
只響應幾個特定的屬性,能夠加入異常處理,修改 __getattr__
方法,以下:
def __getattr__(self, attr): if attr == 'z': return 0 raise AttributeError("Point object has no attribute %s" % attr)
這裏再強調一點,__getattr__
只有在屬性不存在的狀況下才會被調用,對已存在的屬性不會調用 __getattr__
。
與 __getattr__
一塊兒使用的還有 __setattr__
, __delattr__
,相似 obj.attr = value
, del obj.attr
,看下面一個例子:
class Point(object): def __init__(self, x=0, y=0): self.x = x self.y = y def __getattr__(self, attr): if attr == 'z': return 0 raise AttributeError("Point object has no attribute %s" % attr) def __setattr__(self, *args, **kwargs): print 'call func set attr (%s, %s)' % (args, kwargs) return object.__setattr__(self, *args, **kwargs) def __delattr__(self, *args, **kwargs): print 'call func del attr (%s, %s)' % (args, kwargs) return object.__delattr__(self, *args, **kwargs) >>> p = Point(3, 4) call func set attr (('x', 3), {}) call func set attr (('y', 4), {}) >>> p.z 0 >>> p.z = 7 call func set attr (('z', 7), {}) >>> p.z 7 >>> p.w Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 8, in __getattr__ AttributeError: Point object has no attribute w >>> p.w = 8 call func set attr (('w', 8), {}) >>> p.w 8 >>> del p.w call func del attr (('w',), {}) >>> p.__dict__ {'y': 4, 'x': 3, 'z': 7}
咱們通常使用 obj.method()
來調用對象的方法,那能不能直接在實例自己上調用呢?在 Python 中,只要咱們在類中定義 __call__
方法,就能夠對實例進行調用,好比下面的例子:
class Point(object): def __init__(self, x, y): self.x, self.y = x, y def __call__(self, z): return self.x + self.y + z
使用以下:
>>> p = Point(3, 4) >>> callable(p) # 使用 callable 判斷對象是否能被調用 True >>> p(6) # 傳入參數,對實例進行調用,對應 p.__call__(6) 13 # 3+4+6
能夠看到,對實例進行調用就好像對函數調用同樣。
__new__
在 __init__
以前被調用,用來建立實例。__str__
是用 print 和 str 顯示的結果,__repr__
是直接顯示的結果。__getitem__
用相似 obj[key]
的方式對對象進行取值__getattr__
用於獲取不存在的屬性 obj.attr__call__
使得能夠對實例進行調用