在python中,有的名稱會在前面和后面加上兩個下划線,由這些名字組成的集合所包含的方法稱為魔法方法(或者是特殊方法)。如果對象實現了這些方法中的某一個,那么這個方法會在特殊的情況下(確切地說是根據名字)被python調用。而幾乎沒有直接調用它們的必要。
這里會詳細討論一些重要的魔法方法(最重要的是__init__方法和一些處理對象訪問的方法,這些方法允許你創建自己的序列或者是映射),還會處理屬性(通過property函數來處理)。
一. 構造方法
1.1 介紹與創建
首先要討論的第一個魔法方法是構造方法,它代表着類似於以前例子中使用過的那種名為init的初始化方法。但構造方法和其他普通方法不同的地方在於,當一個對象被創建后,會立即調用構造方法。
在python中創建一個構造方法只要將init方法的名字從簡單的init修改為魔法版本__init__即可:
__metaclass__ = type class FooBar: def __init__(self): self.somevar = 42
如果給構造方法傳幾個參數的話,要怎么做呢:
__metaclass__ = type class FooBar: def __init__(self,value = 42): self.somevar = value
因為參數是可選的,所以你可以繼續,當做什么事情都沒發生:
>>> s = FooBar(100) >>> s.somevar 100
1.2 重寫一般方法和特殊的構造方法
重寫是繼承機制中的一個重要內容,對於構造方法尤其重要。構造方法用來初始化新創建對象的狀態,大多數子類不僅要擁有自己的初始化代碼,還要擁有超類的初始化代碼。當一個類的構造方法被重寫,那么就需要調用超類的構造方法,否則對象可能不會被正確地初始化。
__metaclass__ = type class Bird: def __init__(self): self.hungry = True def eat(self): if self.hungry: print "Aaaah..." self.hungry = False else: print 'No,thanks' class SongBird(Bird): def __init__(self): self.sound = 'Squark!' def sing(self): print self.sound
下面介紹2種重寫構造函數的方法:調用超類構造方法的未綁定版本,或者使用super函數。
(1)調用超類構造方法的未綁定版本
class SongBird(Bird): def __init__(self): Bird.__init__(self) self.sound = 'Squark!' def sing(self): print self.sound
在調用一個實例的方法時,該方法的self參數會被自動綁定到實例上。但是如果直接調用類的方法,那么就沒有實例會被綁定。這樣就可以自由第提供需要的self參數。這樣的方法稱為未綁定方法。
通過將當前的實例作為self參數提供給未綁定方法,SongBird就能使用其超類構造方法的所有實現,也就是說屬性hungry能被設置。
(2)使用super函數
如果讀者不想堅守舊版python陣營的話,那么就應該使用super函數。當前的類和對象都可以作為super函數的參數使用,調用函數返回的對象的任何方法都是調用超類的方法,而不是當前類的方法。可以直接使用super(SongBird,self)。
class SongBird(Bird): def __init__(self): super(SongBird,self).__init__() self.sound = 'Squark!' def sing(self): print self.sound
二. 成員訪問
本節會討論一個有用的魔法方法集合,它可以創建行為類似於序列或映射的對象。
2.1 基本的序列和映射規則
序列和映射是對象的集合。為了實現它們的基本行為,如果對象是不可變的,那么就需要2個魔法方法,如果是可變的則需要4個:
(1)__len__(self):這個方法返回集合中所含項目的數量。如果返回0,會被當做一個布爾變量中的假值處理;
(2)__getitem__(self,key):這個方法返回與所給鍵對應的值。
(3)__setitem__(self,key,value):這個方法按照一定的方式存儲和key相關的value,該值隨后可使用__getitem__來獲取。當然,只能為可修改對象定義這個方法。
(4)__delitem__(self,key):這個方法都在對一部分對象使用del語句時被調用,同時必須刪除和元素相關的鍵。
2.2 子類化列表,字典和字符串
標准庫有3個關於序列和映射規則(UserList,UserString和UserDict)可以立即使用的實現“只想在一個操作中自定義行為,那么其他的方法都不要實現”。在較新版本的Python中,可以子類化內建類型。
因此,如果希望實現一個和內建列表行為類似的序列,可以使用子類list。下面來看看帶訪問計數的列表:
__metaclass__ = type class CouterList(list): def __init__(self,*arg): super(CounterList,self).__init__(*arg) self.counter = 0 def __getitem__(self,index): self.counter += 1 return super(CounterList,self).__getitem__(index)
CountList類嚴重依賴於它的子類化超類(list)的行為,它沒有重寫任何的方法都能被直接使用。在2個被重寫的方法中,super方法被用來調用相應的超類的方法,只在__init__中添加了所需的初始化counter特性的行為,並在__getitem__中更新了counter特性。
三. 屬性
訪問器是一個簡單的方法,它能夠使用getHeight,setHeight這樣的名字來得到或者重綁定一些特性。如果在訪問給定的特性時必須要采取一些行動,那么像這樣的封裝狀態變量就很重要。
3.1 property函數
__metaclass__ = type class Rectangle: def __init__(self): self.width = 0 self.height = 0 def setSize(self,size): self.width,self.height = size def getSize(self): return self.width,self.height
上面的代碼中,當計算面積或者對角線長度時就要考慮size是怎么實現的,如果將size變成一個真正的特性,這樣width和height就能動態算出。那么怎么解決呢?把所有屬性都放到訪問器方法中?但是如果有很多簡單的特性,那就要寫很多訪問器方法了,它們除了返回或者設置特性就不做任何事了。
幸好,python能隱藏訪問器方法,讓所有特性看起來一樣,這些通過訪問器定義的特性被稱為屬性,在新式類中可以使用property函數,創建屬性。
它的使用很簡單,只需要增加一行代碼:
__metaclass__ = type class Rectangle: def __init__(self): self.width = 0 self.height = 0 def setSize(self,size): self.width,self.height = size def getSize(self): return self.width,self.height size = property(getSize,setSize)
property函數創建了一個屬性,其中訪問器函數被用做參數,這個屬性命名為size,這樣一來就不用擔心它是怎么實現的了。可以用同樣的方式處理width,height和size:
>>> r = Rectangle() >>> r.width = 10 >>> r.height = 5 >>> r.size (10, 5) >>> r.size = 150,100 >>> r.width 150
很明顯,size特性仍然取決於getSize和setSize中的計算,但它看起來就像普通的屬性一樣。
實際上,property函數可以用0,1,2,3或者4個參數來調用。如果沒有參數,產生的屬性既不可讀,也不可寫。如果只使用一個參數(一個取值方法),產生的屬性是只讀的。第三個參數是一個用於刪除特性的方法,第四個參數是一個文檔字符串。property的4個參數分別被叫做fget,fset,fdel和doc。
理論上說,在新式類中應該使用property函數而不是訪問器方法。
3.2 靜態方法和類成員方法
靜態方法和類成員方法分別在創建時分別被裝入Staticmethod類型和Classmethod類型的對象中。靜態方法的定義沒有self參數,且能夠被類本身直接調用。類方法在定義時需要名為cls的類似於self的參數,類成員方法可以直接被類的具體對象調用,但cls參數是自動被綁定到類的。
在python2.4中,為這樣的包裝方法引入了一個叫做裝飾器的新語法,使用@操作符,在方法(或者函數)的上方將裝飾器列出,從而指定一個或者更多的裝飾器。
__metaclass__ = type class MyClass: @staticmethod def smeth(): print 'this is a static method' @classmethod def cmeth(cls): print 'this is a class method of ',cls
定義好之后,可以如下那樣使用:
>>> MyClass.smeth() this is a static method >>> MyClass.cmeth() this is a class method of <class '__main__.MyClass'>
3.3 __getattr__,__setattr__和它的朋友們
攔截對象的所有特性訪問是可能的,這樣可以用舊式類實現屬性。為了在訪問特性時可以執行代碼,必須使用一些魔法方法:(在舊式類中只需要后3個)
(1)__getattribute__(self,name):當特性name被訪問時自動被調用(只能在新式類中使用);
(2)__getattr__(self,name):當特性name被訪問且對象沒有相應的特性時被自動調用;
(3)__setattr__(self,name,value):當試圖給name賦值時會被自動調用;
(4)__delattr__(self,name):當試圖刪除特性name時被自動調用;
__metaclass__ = type class Rectangle: def __init__(self): self.width = 0 self.height = 0 def __setattr__(self,name,value): if name == 'size': self.width,self.height = size else: self.__dict__[name] = value def __getattr__(self,name): if name == 'size': return self.width,self.height else: raise AttributeError
