Reproduce from
簡介
什么是魔法方法呢?他們在面向?qū)ο蟮?Python 處處皆是。他們是一些可以讓你對類添加「魔法」的特殊 function。他們經(jīng)常是兩個(gè)下劃線包圍來命名的(比如 __init__,__lt__)。
構(gòu)造方法
最被熟知的基本魔法方法就是 init,我們可以用它來指明一個(gè)對象初始化的行為。然而,當(dāng)我們調(diào)用 x = SomeClass() 的時(shí)候,init 并不是第一個(gè)被調(diào)用的方法。事實(shí)上,第一個(gè)被調(diào)用的是 new,這個(gè)方法才真正地創(chuàng)建了實(shí)例。當(dāng)這個(gè)對象的生命周期結(jié)束的時(shí)候,del 會被調(diào)用。
- __new__(cls, [...])
__new__是對象實(shí)例話時(shí)第一個(gè)調(diào)用的方法,它只取cls參數(shù),并把其他參數(shù)傳給__init__。__new__很少使用,但是也有它適合的場景,尤其是當(dāng)類繼承自一個(gè)像元組或者字符串這樣不經(jīng)常改變的類型的時(shí)候。 - __init__(self, [...])
類的初始化方法。它獲取任何傳給構(gòu)造器的參數(shù)(比如調(diào)用x = SomeClass(10, 'foo')),__init__就會接到參數(shù)10和foo。__init__在 Python 的定義中用的最多。 - __del__(self)
__new__和__init__是對象的構(gòu)造器。__del__是對象的銷毀器。它并非實(shí)現(xiàn)了語句del x(因此該語句不等同于x.__del__())。而是定義了當(dāng)對象被垃圾回收時(shí)的行為。當(dāng)對象需要在銷毀時(shí)做一些處理的時(shí)候,這個(gè)方法很有用,比如socket對象,文件對象。但是需要注意的是,當(dāng) Python 解釋器對出但對象仍然存活的時(shí)候,__del__并不會執(zhí)行。所以養(yǎng)成手工清理的好習(xí)慣是很重要的,比如即使關(guān)閉連接。
class FileObject(object):
'''文件對象的裝飾類,用來保證文件被刪除時(shí)能夠正確關(guān)閉。'''
def __init__(self, file_path='~', file_name='sample.text'):
# 使用讀寫模式打開filepath中的filename文件
self.file = open(join(file_path, file_name), 'r+')
def __del__(self):
self.file.close()
def self.file
操作符
使用Python魔法方法的一個(gè)巨大優(yōu)勢就是可以構(gòu)建一個(gè)擁有Python內(nèi)置類型行為的對象。這意味著你可以避免使用非標(biāo)準(zhǔn)的、丑陋的方式來表達(dá)簡單的操作。 在一些語言中,這樣做很常見:
if instance.equals(other_instance):
# do something
你當(dāng)然可以在 Python 里也這么做,但是這張做讓代碼變的冗長而混亂。不同的類庫可能對同一種比較操作采用不同的方法名稱,這讓使用者需要很多沒有必要的工作。運(yùn)用魔法方法的魔力,可以定義方法 __eq__。
if instance == other_instance:
# do something
比較操作符
Python 包含了一系列的魔法方法,用于實(shí)現(xiàn)對象之間直接比較,而不需要采用方法調(diào)用。同樣也可以重載 Python 默認(rèn)的比較方法,改變他們的行為。
- __cmp__(self, other)
__cmp__是所有比較魔法方法中最基礎(chǔ)的一個(gè),它實(shí)際上定義了所有比較操作符的行為(<,==,!=,等等),但是他可能不能按照你需要的方式工作(例如,判斷一個(gè)實(shí)例和另一個(gè)實(shí)例是否相等采用一套標(biāo)準(zhǔn),而與判斷一個(gè)實(shí)例是否大于另一實(shí)例采用另一套)。__cmp__應(yīng)該在self < other是返回一個(gè)負(fù)整數(shù),在self == other時(shí)返回0,在self > other時(shí)返回正整數(shù)。最好只定義你所需要的比較形式,而不一次定義全部。如果你需要實(shí)現(xiàn)所有的比較形式,而且他們的判斷標(biāo)準(zhǔn)類似,那么__cmp__是一個(gè)很好的方法,可以減少代碼重復(fù),讓代碼更簡潔。 - __eq__(self, other)
定義等于操作符==的行為。 - __ne__(self, other)
定于不等于操作符!=的行為。 - __lt__(self, other)
定義小于操作符<的行為。 - __gt__(self, other)
定義大于操作符>的行為。 - __le__(self, other)
定義小于等于操作符<=的行為。 - __ge__(self, other)
定義大于等于操作符>=的行為。
假如我們用一個(gè)類來存儲單詞。我們可能想按照字典序(字母順序)來比較單詞,字符串的默認(rèn)比較行為就是這樣。我們可能也想按照其他規(guī)則來比較字符串,像是長度,或者音節(jié)的數(shù)量。在這個(gè)例子中,我們使用長度作為比較標(biāo)準(zhǔn),下面是一種實(shí)現(xiàn):
class Word(str):
'''單詞類,按照單詞長度來定義比較行為'''
def __new__(cls, word):
# 注意,我們只能使用 __new__,因?yàn)?str 是不可變類型
# 所以我們必須提前初始化它(在實(shí)例創(chuàng)建時(shí))
if ' ' in word:
print("Value contains spaces. Truncating to first space.")
word = word[:word.index(' ')]
# Word現(xiàn)在包含第一個(gè)空格前的所有字母
return str.__new__(cls, word)
def __gt__(self, other):
return len(self) > len(other)
def __lt__(self, other):
return len(self) < len(other)
def __ge__(self, other):
return len(self) >= len(other)
def __le__(self, other):
return len(self) <= len(other)
現(xiàn)在我們可以創(chuàng)建兩個(gè) Word 對象(Word('foo') 和 Word('bar'))然后根據(jù)長度來比較他們。注意我們沒有定義 __eq__ 和 __ne__,這是因?yàn)橛袝r(shí)候他們會導(dǎo)致奇怪的結(jié)果(Word('foo') == Word('bar') 得到的結(jié)果會是 True)。根據(jù)長度測試是否相等毫無意義,所以我們使用 str 的實(shí)現(xiàn)來比較相等。
不需要實(shí)現(xiàn)所有的比較方法,就可以使用豐富的比較操作。標(biāo)準(zhǔn)庫還在 functools 模塊中提供了一個(gè)類裝飾器,只要我們定義__eq__ 和 另一個(gè)操作符(__gt__,__lt__ 等),它就可以幫我們實(shí)現(xiàn)比較方法。這個(gè)特性只在 Python 2.7 中可用。當(dāng)它可用時(shí),它能幫助我們節(jié)省大量的時(shí)間和精力。要使用它,只需要它 @total_ordering 放在類的定義之上就可以了。
數(shù)值操作符
就像你可以使用比較操作符來比較類的實(shí)例,你也可以定義數(shù)值操作符的行為。這樣的操作符真的很多。把它們分為五類:一元操作符,常見算數(shù)操作符,反射算數(shù)操作符(后面會涉及更多),增強(qiáng)賦值操作符,和類型轉(zhuǎn)換操作符。
一元操作符
一元操作符只有一個(gè)操作符。
- __pos__(self)
實(shí)現(xiàn)取正操作,例如+some_object - __neg__(self)
實(shí)現(xiàn)取負(fù)操作,例如-some_object - __abs__(self)
實(shí)現(xiàn)內(nèi)建絕對值函數(shù)abs()操作 - __invert__(self)
實(shí)現(xiàn)取反操作符~ - __rount__(self, n)
實(shí)現(xiàn)內(nèi)建函數(shù)round(),n 是近似小數(shù)點(diǎn)的位數(shù) - __floor__(self)
實(shí)現(xiàn)math.floor()函數(shù),即向下取整 - __ceil__(self)
實(shí)現(xiàn)math.ceil()函數(shù),即向上取整 - __trunc__(self)
實(shí)現(xiàn) math.trunc() 函數(shù),即距離零最近的整數(shù)
常見算數(shù)操作符
常見的二元操作符(和一些函數(shù)),像 +,-,* 之類的,它們很容易從字面意思理解。
- __add__(self, other)
實(shí)現(xiàn)加法操作 - __sub__(self, other)
實(shí)現(xiàn)減法操作 - __mul__(self, other)
實(shí)現(xiàn)乘法操作 - __floordiv__(self, other)
實(shí)現(xiàn)使用//操作符的整數(shù)除法 - __div__(self, other)
實(shí)現(xiàn)使用/操作符的除法 - __truediv__(self, other)
實(shí)現(xiàn)true除法,這個(gè)函數(shù)只有使用from __future__ import division時(shí)才有作用 - __mod__(self, other)
實(shí)現(xiàn) % 取余操作符 - __divmod__(self, other)
實(shí)現(xiàn) divmod 內(nèi)建函數(shù) - __pow__
實(shí)現(xiàn) ** 操作符 - __lshift__(self, other)
實(shí)現(xiàn)左移位運(yùn)算符 << - __rshift__(self, other)
實(shí)現(xiàn)右移位運(yùn)算符 >> - __and__(self, other)
實(shí)現(xiàn)按位與運(yùn)算符 & - __or__(self, other)
實(shí)現(xiàn)按位或運(yùn)算符 | - __xor__(self, other)
實(shí)現(xiàn)按位異或運(yùn)算符 ^
反射算數(shù)運(yùn)算符
反射運(yùn)算符,舉個(gè)例子:
some_ohter + other
這是"常見"的加法,反射是一樣的意思,只不過運(yùn)算符交換了一下位置:
other + some_object
所有反射運(yùn)算符魔法方法和它們的常見版本做的工作相同,只不過是處理交換兩個(gè)操作數(shù)之后的情況。絕大多數(shù)情況下,反射運(yùn)算和正常順序產(chǎn)生的結(jié)果是相同的,所以很可能定義 radd 時(shí)只是調(diào)用一下 add。注意,操作符左側(cè)的對象(也就是上面的 other)一定不要定義(或產(chǎn)生 NotImplemented 異常)操作符的非反射版本。例如,在上面的例子中,只有當(dāng) other 沒有定義 add 時(shí) some_other.radd 才會被調(diào)用。
- __radd__(self, other)
實(shí)現(xiàn)反射加法操作 - __rsub__(self, other)
實(shí)現(xiàn)反射減法操作 - __rmul__(self, other)
實(shí)現(xiàn)反射乘法操作 - __rfloordiv__(self, other)
實(shí)現(xiàn)使用//操作符的整數(shù)反射除法 - __rdiv__(self, other)
實(shí)現(xiàn)使用/操作符的反射除法 - __truediv__(self, other)
實(shí)現(xiàn)true反射除法,這個(gè)函數(shù)只有使用from __future__ import division時(shí)才有作用 - __rmod__(self, other)
實(shí)現(xiàn) % 反射取余操作符 - __rdivmod__(self, other)
實(shí)現(xiàn)調(diào)用divmod(other, self)時(shí)divmod內(nèi)建函數(shù)的操作。 - __rpow__
實(shí)現(xiàn) ** 反射操作符 - __rlshift__(self, other)
實(shí)現(xiàn)反射左移位運(yùn)算符 << - __rrshift__(self, other)
實(shí)現(xiàn)反射右移位運(yùn)算符 >> - __rand__(self, other)
實(shí)現(xiàn)反射按位與運(yùn)算符 & - __ror__(self, other)
實(shí)現(xiàn)反射按位或運(yùn)算符 | - __rxor__(self, other)
實(shí)現(xiàn)反射按位異或運(yùn)算符 ^
增強(qiáng)賦值運(yùn)算符
Python 提供了大量的魔術(shù)方法,可以用來自定義增強(qiáng)賦值操作的行為。它融合了 "常見" 的操作符和復(fù)制操作,例子:
x = 5
x += 1 # 也就是 x = x + 1
這些方法都應(yīng)該返回左側(cè)操作數(shù)應(yīng)該被賦予的值(例如,a += b __iadd__ 也許會返回 a + b,這個(gè)結(jié)果會賦給 a),下面是方法列表:
- __iadd__(self, other)
實(shí)現(xiàn)加法復(fù)制操作 - __isub__(self, other)
實(shí)現(xiàn)減法賦值操作 - __imul__(self, other)
實(shí)現(xiàn)乘法賦值操作 - __ifloordiv__(self, other)
實(shí)現(xiàn)使用//=操作符的整數(shù)除法賦值操作 - __idiv__(self, other)
實(shí)現(xiàn)使用/=操作符的除法賦值操作 - __iturediv__(self, other)
實(shí)現(xiàn) true 除法賦值操作,這個(gè)函數(shù)只有使用from __future__ import division時(shí)才有作用 - __imod__(self, other)
實(shí)現(xiàn)實(shí)現(xiàn)%=取余賦值操作 - __ipow__
實(shí)現(xiàn)**=操作 - __ilshift__(self, other)
實(shí)現(xiàn)左移位賦值運(yùn)算符<<= - __irshift__(self, other)
實(shí)現(xiàn)右移位賦值運(yùn)算符>>= - __iand__(self, other)
實(shí)現(xiàn)按位與運(yùn)算符&= - __ior__(self, other)
實(shí)現(xiàn)按位異或賦值運(yùn)算符| - __ixor__(self, other)
實(shí)現(xiàn)按位異或賦值運(yùn)算符^=
類型轉(zhuǎn)換運(yùn)算符
Python 也有一系列的魔法方法用于實(shí)現(xiàn)類似 float() 的內(nèi)建類型轉(zhuǎn)換函數(shù)的操作:
- __int__(self)
實(shí)現(xiàn)到 int 類型的轉(zhuǎn)換 - __long__(self)
實(shí)現(xiàn)到 long 的類型轉(zhuǎn)換 - __float__(self)
實(shí)現(xiàn)到 float 的類型轉(zhuǎn)換 - __complex__(self)
實(shí)現(xiàn)到 complex 的類型轉(zhuǎn)換
... - __index__(self)
實(shí)現(xiàn)當(dāng)對象用于切片表示時(shí)到一個(gè)整數(shù)的類型轉(zhuǎn)換。如果你定義了一個(gè)可能會用于切片操作的數(shù)值類型,你應(yīng)該定義__index__ - __trunc__(self)
當(dāng)調(diào)用math.trunc(self)時(shí)調(diào)用該方法,__trunc__應(yīng)該返回self截取到一個(gè)整數(shù)類型(通常是 long 類型)的值 - __coerce__(self)
該方法用于實(shí)現(xiàn)混合模式算術(shù)運(yùn)算,如果不能進(jìn)行類型轉(zhuǎn)換,__coerce__應(yīng)該返回 None。反之,它應(yīng)該返回一個(gè)二元組self和other,這兩者均已被轉(zhuǎn)換成相同的類型
類的表示
使用字符串來表示類是一個(gè)相當(dāng)有用的特性。在 Python 中有一些內(nèi)建方法可以返回類的表示,相對應(yīng)的,也有一系列魔法方法可以用來自定義在使用這些內(nèi)建函數(shù)式類的行為。
- __str__(self)
定義對類的實(shí)例調(diào)用str()時(shí)的行為 - __repr__(self)
定義對類的實(shí)例調(diào)用repr()時(shí)的行為。str()和repr()最主要的差別在于 “目標(biāo)用戶”。repr()的作用是產(chǎn)生機(jī)器可讀的輸出(在大部分情況下,起輸出可以作為有效的 Python 代碼),而str()側(cè)產(chǎn)生人類可讀的輸出。 - __unicode__(self)
定義對類的實(shí)例調(diào)用unicode()時(shí)的行為。unicode()和str()很像,只是它返回 unicode 字符串。注意,如果調(diào)用者試圖調(diào)用str()而你的類只實(shí)現(xiàn)了__unicode__字符串,那么類將不能正常工作。所以你應(yīng)該總定義__str__(),以防有些人沒有使用unicode()。 - __format__(self)
定義當(dāng)類的實(shí)例用于新式字符串格式化時(shí)的行為,例如,"Hello,{}:abc!".format("abc")會導(dǎo)致調(diào)用a.__format__("abc")。當(dāng)定義你自己的數(shù)值類型或字符串類型時(shí),你可能想提供某些特殊的格式化選項(xiàng),這種情況下這個(gè)魔法方法會非常有用。 - __hash__(self)
定義對類的實(shí)例調(diào)用hash()時(shí)的行為。它必須返回一個(gè)整數(shù),其結(jié)果會被用于字典中鍵的快速比較。同時(shí)注意一點(diǎn),實(shí)現(xiàn)這個(gè)魔法方法通常也需要實(shí)現(xiàn)__eq__,并且遵守如下的規(guī)則:a == b意味著hash(a) == hash(b)。 - __nonzero__(self)
定義對類的實(shí)例調(diào)用bool()時(shí)的行為,根據(jù)你自己對類的設(shè)計(jì),針對不同的實(shí)例,這個(gè)魔法方法應(yīng)該相應(yīng)地返回True或False。 - __dir__(self)
定義對類的實(shí)例調(diào)用 dir() 時(shí)的行為,這個(gè)方法應(yīng)該像調(diào)用者返回一個(gè)屬性列表。一般來說,沒必要自己實(shí)現(xiàn)__dir__。但是如果你重定義了__getattr__或者__getattribute__,乃至使用動態(tài)生成的屬性,以實(shí)現(xiàn)類的交互式使用,那么這個(gè)魔法方法是必不可少的。
訪問控制
很多從其他語言轉(zhuǎn)向 Python 的人抱怨 Python 的類缺少真正意義上的封裝(即沒辦法定義私有屬性然后使用共有的 getter 和 setter)。然而事實(shí)并非如此。實(shí)際上 Python 不是通過現(xiàn)實(shí)定義的字段和方法修改器,而是通過魔法方法實(shí)現(xiàn)了一系列的封裝。
- __getattr__(self, name)
當(dāng)用戶試圖訪問一個(gè)根本不存在的(或者暫時(shí)不存在)屬性時(shí),你可以通過這個(gè)魔法方法來定義類的行為。這個(gè)可以用于捕捉錯(cuò)誤的拼寫并且給出指引,使用廢棄屬性時(shí)給出警告(如果你愿意,仍然可以計(jì)算并且返回該屬性),以及靈活地處理 AttributeError。只有當(dāng)試圖訪問不存在的屬性時(shí)它才會被調(diào)用,所以這不能算是一個(gè)真正的封裝的辦法。 - __setattr__(self, name, value)
和__getattr__不同,__setattr__可以用于真正意義上的封裝。它允許你定義某個(gè)屬性的賦值行為,不管這個(gè)屬性存在與否,也就是說你可以對任意屬性的任何變化都定義自己的規(guī)則。然后,一定要小心使用__setattr__! - __delattr__(self, name)
這個(gè)魔法方法和__setattr__幾乎相同,只不過它是用于處理刪除屬性時(shí)的行為。和__setattr__一樣,使用它時(shí)也需要多加小心,防止產(chǎn)生無限遞歸(在__delattr__的實(shí)現(xiàn)中調(diào)用 del self.name 會導(dǎo)致無限遞歸)。 - __getattribute__(self, name)
__getattribute__看起來和上面那些方法很合得來,但是最好不要使用它。__getattribute__只能用新式類。在最新版的 Python 中所有的類都是新式類。在最新版的 Python 中所有的類都是新式類,在老版 Python 中可以使用繼承object來創(chuàng)建新式類。__getattribute__允許你自定義屬性被訪問時(shí)的行為,它也同樣可能遇到無限遞歸問題(通過調(diào)用基類的__getattribute__來避免)。__getattribute__基本上可以替代__getattr__。只有當(dāng)它被實(shí)現(xiàn),并且顯示地被調(diào)用,或者產(chǎn)生AttributeError時(shí)它才被使用。這個(gè)魔法方法可以被使用,不過不推薦使用它,因?yàn)樗氖褂梅秶鄬τ邢蓿ㄍǔN覀兿胍谫x值時(shí)進(jìn)行特殊操作,而不是取值時(shí)),而且實(shí)現(xiàn)這個(gè)方法很容易出現(xiàn) Bug。
自定義這些控制屬性訪問的魔法方法很容易導(dǎo)致問題,考慮下面這個(gè)例子:
def __setattr__(self, name, value):
self.name = value
# 因?yàn)槊看螌傩再x值都要調(diào)用 __setattr__(),所以這里的實(shí)現(xiàn)會導(dǎo)致遞歸
# 這里的調(diào)用實(shí)際上是 self.__setattr('name', value)。因?yàn)檫@個(gè)方法一直
# 在調(diào)用自己,因此遞歸將持續(xù)進(jìn)行,知道程序崩潰
def __setattr__(self, name, value):
self.__dict__[name] = value # 使用 __dict__ 進(jìn)行賦值
# 自定義行為
再次重申,Python 的魔法方法十分強(qiáng)大,能力越強(qiáng)責(zé)任越大,了解如何正確的使用魔法方法更加重要。
到這里,我們對 Python 的自定義屬性存取控制有了什么樣的印象?它并不適合輕度的使用。實(shí)際上,它有些過份強(qiáng)大,而且違反直覺。然而它之所以存在,是因?yàn)橐粋€(gè)更大的原則:** Python 不指望杜絕壞事發(fā)生,而是想辦法讓做壞事變得困難。**自由是至高無上的權(quán)利,你真的可以隨心所欲。下面的例子展示了實(shí)際應(yīng)用中某些特殊的屬性訪問方法(注意我們之所以使用 super 是因?yàn)椴皇撬械念惗?__dict__ 屬性):
class AccessCounter(object):
''' 一個(gè)包含了一個(gè)只并且實(shí)現(xiàn)了訪問計(jì)數(shù)器的類
每次值的變化都會導(dǎo)致計(jì)數(shù)器自增'''
def __init__(self, val):
super(AccessCounter, self).__setattr__('counter', 0)
super(AccessCounter, self).__setattr__('value', val)
def __setattr__(self, name, value):
if name == 'value':
super(AccessCounter, self).__setattr__('counter', self.counter + 1)
# 使計(jì)數(shù)器自增變成不可避免
# 如果你想阻止其他屬性的賦值行為
# 產(chǎn)生 AttributeError(name) 就可以了
super(AccessCounter, self).__setattr__(name, value)
def __delattr__(self, name):
if name == 'value':
super(AccessCounter, self).__setattr__('counter', self.counter + 1)
super(AccessCounter, self).__delattr(name)
自定義序列 Making Custom Sequences
有許多魔法辦法可以讓你的 Python 類表現(xiàn)得像是內(nèi)建序列類型(字典,元組,列表,字符串等)。他們給了你難以置信的控制能力,可以讓你的類與一系列的全局函數(shù)完美結(jié)合。
預(yù)備知識 Requirements
既然講到創(chuàng)建自己的序列類型,就不得不說協(xié)議 protocols。協(xié)議類似某些語言中的接口,里面包含的是一些必須實(shí)現(xiàn)的方法。在 Python 中,協(xié)議完全是非正式的(totally informal),也不需要顯式的聲明,實(shí)際上,他們更像是一種參考標(biāo)準(zhǔn) guildlines。
在 Python 的實(shí)現(xiàn)自定義容器類型需要用到一些協(xié)議。首先,不可變?nèi)萜黝愋陀腥缦聟f(xié)議:想實(shí)現(xiàn)一個(gè)不可變?nèi)萜?,你需要定義 __len__ 和 __getitem__??勺?nèi)萜鞯膮f(xié)議除了上面的兩個(gè)方法外,還需要定義 __setitem__ 和 __delitem__。最后,如果想讓對象可以迭代,需要定義 __iter__,這個(gè)方法返回一個(gè)迭代器。迭代器必須遵守迭代器協(xié)議,需要定義 __iter__(返回它自己)和 next 方法。
容器背后的魔法方法
- __len__(self)
返回容器的長度,可變和不可變類型都需要實(shí)現(xiàn)。 - __getitem__(self, key)
定義對容器中某一項(xiàng)使用self[key]的方式進(jìn)行讀取操作時(shí)的行為。這也是可變和不可變?nèi)萜黝愋投夹枰獙?shí)現(xiàn)的一個(gè)方法。它應(yīng)該在鍵的類型錯(cuò)誤時(shí)產(chǎn)生 TypeError 異常,同時(shí)在沒有鍵值相匹配的內(nèi)容是產(chǎn)生 KeyError 異常。 - __setitem__(self, key)
定義對容器中某一項(xiàng)使用self[key]的方式進(jìn)行賦值操作時(shí)的行為。它是可變?nèi)萜黝愋捅仨殞?shí)現(xiàn)的一個(gè)方法,童顏應(yīng)該在合適的時(shí)候產(chǎn)生 KeyError 和 TypeError 異常。 - __iter__(self, key)
它應(yīng)該返回當(dāng)前容器的一個(gè)迭代器。迭代器以一連串內(nèi)容的形式返回,最常見的是使用iter()函數(shù)調(diào)用,以及在類似for x in container的循環(huán)中被調(diào)用。迭代器是他們自己的隊(duì)形,需要定義__iter__方法并在其中返回自己。 - __reversed__(self)
定義了對容器使用reversed()內(nèi)建函數(shù)時(shí)的行為。它應(yīng)該返回一個(gè)反轉(zhuǎn)之后的序列。當(dāng)你的序列類是有序時(shí),類似列表和元組,再實(shí)現(xiàn)這個(gè)方法。 - __contains__(self, item)
__contains__定義了使用in和not in進(jìn)行成員測試時(shí)類的行為。這個(gè)方法不是序列協(xié)議的一部分,原因是,如果__contains__沒有定義,Python 就會迭代整個(gè)序列,如果找到了需要的一項(xiàng)就返回 Ture。 - __missing__(self, key)
__missing__在字典的子類中使用,他定義了當(dāng)試圖方位一個(gè)字典中不存在的鍵時(shí)的行為(目前為止是指字典的實(shí)例,例如我有一個(gè)字典 d , 'george' 不是字典中的一個(gè)鍵,當(dāng)試圖訪問d['george']時(shí)就會調(diào)用d.__missing__('george'))。
例子
一個(gè)實(shí)現(xiàn)了一些函數(shù)式結(jié)構(gòu)的列表
class FunctionalList:
''' 一個(gè)列表的封裝類,實(shí)現(xiàn)了一些額外的函數(shù)式方法
例如 head, tail, init, last, drop 和 take
'''
def __init__(self, values=None):
if values if None:
self.values = []
else:
self.values = values
def __len__(self):
return len(self.values)
def __getitem__(self, key):
# 如果鍵的類型或值不合法,列表會返回異常
return self.values[key]
def __setitem__(self, key, value):
self.values[key] = value
def __delitem__(self, key):
del self.values[key]
def __iter__(self):
return iter(self.values)
def __reversed__(self):
return reversed(self.values)
def append(self, value):
self.values.append(value)
def head(self):
# 取得第一個(gè)元素
return self.values[0]
def tail(self):
# 取得除第一個(gè)元素外的所有元素
return self.values[1:]
def init(self):
# 取得除最后一個(gè)元素外的所有元素
return self.values[:-1]
def last(self):
# 取得最后一個(gè)元素
return self.values[-1]
def drop(self, n):
# 取得除前n個(gè)元素外的所有元素
return self.values[n:]
def take(self, n):
# 取得前n個(gè)元素
return self.values[:n]
當(dāng)然,自定義序列有更大的用處,而且絕大部分都在標(biāo)準(zhǔn)庫中實(shí)現(xiàn)了(Python 都是自帶庫的),像 Counter, OrderedDict 和 NamedTuple。
反射 Reflection
可以通過定義魔法方法來控制用于反射的內(nèi)建函數(shù) isinstance 和 issubclass 的行為。
- __instancecheck__(self, instance)
檢查一個(gè)實(shí)例是否是你定義的類的一個(gè)實(shí)例(例如isinstance(instance, class))。 - __subclasscheck__(self, subclass)
檢查一個(gè)類是否是你定義的類的子類(例如insubclass(subclass, class))。
這幾個(gè)魔法方法的使用范圍有些窄。相比其他魔法方法他們顯得不是很重要。但是他們展示了在 Python 中進(jìn)行面向?qū)ο缶幊虝r(shí)很重要的一點(diǎn):不管做什么事情,都會有一個(gè)簡單方法,不管它用不常用。這些魔法方法可能看起來沒那么有用,但是當(dāng)你正正需要用到他們的時(shí)候,你會感到幸運(yùn),因?yàn)樗麄冞€在那兒。
抽象基類
請參考 http://docs.python.org/2/library/abc.html
可調(diào)合的對象
在 Python 中,函數(shù)時(shí)一等的對象 (functions are first-class objects)。這意味著他們可以像其他任何對象一樣被傳遞到函數(shù)和方法中,這是一個(gè)十分強(qiáng)大的特性。
Python 中一個(gè)特殊的魔法方法允許你自己類的對象表現(xiàn)得像是函數(shù),然后你就可以“調(diào)用”他們,把它們傳遞到使用函數(shù)做參數(shù)的函數(shù)中,等等。這是另一個(gè)強(qiáng)大而且方便的特性,讓使用 Python 編程變得更加幸福。
-
__call__(self, [args...])
允許類的一個(gè)實(shí)例像函數(shù)那樣被調(diào)用。本質(zhì)上代表了x()和x.__call__()是相同。注意,__call__可以有多個(gè)參數(shù),這代表你可以像定義其他任何函數(shù)一樣,定義__call__,喜歡用多少參數(shù)就用多少。__call__在某些需要經(jīng)常改變狀態(tài)的類的實(shí)例中顯得特別有用?!罢{(diào)用”這個(gè)實(shí)例來改變他的狀態(tài),是一種更加符合直覺,也更加優(yōu)雅的方法。一個(gè)表示平面上實(shí)體的類是一個(gè)不錯(cuò)的例子:
class Entity:
''' 表示一個(gè)實(shí)體的類,調(diào)用它的實(shí)例可以更新實(shí)體的位置 entity's position'''
def __init__(self, size, x, y):
self.x, self.y = x, y
self.size = size
def __call__(self, x, y):
'''改變實(shí)體的位置'''
self.x, self.y = x, y
上下文管理器 Context Manager
在 Python 2.5 中引入了一個(gè)全新的關(guān)鍵詞,隨之而來的是一種新的代碼復(fù)用方法 -- with 聲明。上下文管理的概念在 Python 中并不是全新引入的(之前他作為標(biāo)準(zhǔn)庫的一部分實(shí)現(xiàn)),直到 PEP 343 被接受,它才成為一種一級的語言結(jié)構(gòu)。可能你已經(jīng)見過這種寫法了:
with open('foo.txt') as bar:
# 使用 bar 進(jìn)行某些操作
當(dāng)對象使用 with 聲明創(chuàng)建時(shí),上下文管理器允許類做一些設(shè)置和清理工作。上下文管理器的行為有下面兩個(gè)魔方方法所定義:
- __enter__(self)
定義使用with聲明創(chuàng)建的語句塊最開始上下文管理器應(yīng)該做些什么。注意__enter__的返回值會賦給with聲明的目標(biāo),也就是as之后的東西。 - __exit__(self, exception_type, exception_value, traceback)
定義當(dāng) with 聲明語句塊執(zhí)行完畢(或終止)時(shí)上下文管理器的行為。它可以用來處理異常,進(jìn)行清理,或者做其他應(yīng)該在語句塊結(jié)束之后立刻執(zhí)行的工作。如果語句塊順利執(zhí)行,exception_type,exception_value和traceback會是None。否則,你可以選擇處理這個(gè)異?;蛘咦層脩魜硖幚怼H绻阆胩幚懋惓?,確保__exit__在完成工作之后返回True。如果你不想處理異常,那就讓它發(fā)生吧。
對一些具有良好定義的且通用的設(shè)置和清理行為的類,__enter__ 和 __exit__ 會變得特別有用。你也可以使用這幾個(gè)方法來創(chuàng)建通用的上下文管理器,用來包裝其他對象。下面是一個(gè)例子:
class Closer():
"""一個(gè)上下文管理器,可以在 with 語句中
使用 close() 自動關(guān)閉對象"""
def __init__(self, obj):
self.obj = obj
def __enter__(self, obj):
return self.obj # 綁定到目標(biāo)
def __exit__(self, exception_type, exception_value, traceback):
try:
self.obj.close()
except AttributeError: # obj 不是可關(guān)閉的
print 'Not cloaseable.'
return True
這是一個(gè) Closer 在實(shí)際使用中的例子,使用一個(gè) FTP 連接演示(一個(gè)可關(guān)閉的 socket):
>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
... conn.dir()
...
# 為了簡單,省略了某些輸出
>>> conn.dir()
... # 很長的 AttributeError 信息,不能使用一個(gè)已關(guān)閉的連接
>>> with Closer(int(5)) as i:
... i += 1
...
Not closable.
>>> i
6
看到我們的包裝器是如何同時(shí)優(yōu)雅地處理正確和不正確的調(diào)用了嗎?這就是上下文管理器和魔法方法的力量。Python 標(biāo)準(zhǔn)庫包含一個(gè) contextlib 模塊,里面有一個(gè)上下文管理器 contextlib.closing() 基本上和我們的包裝器完成的是同樣的事情(但是沒有包含任何當(dāng)對象沒有 close() 方法時(shí)的處理)。
創(chuàng)建描述符對象 Building Descriptor Objects
描述符 Descriptor 是一個(gè)類,當(dāng)使用取值,賦值,和刪除時(shí)它可以改變其它對象。描述符不是用來單獨(dú)使用的,它們需要被一個(gè)擁有著類所包含(they're meant to be held by an owner class)。描述符可以用來創(chuàng)建面向?qū)ο髷?shù)據(jù)庫,以及創(chuàng)建某些屬性之間互相依賴的類。描述符在表現(xiàn)具有不同單位的屬性,或者需要計(jì)算的屬性時(shí)顯得特別有用(例如表現(xiàn)一個(gè)坐標(biāo)系中的點(diǎn)的類,其中的距離原點(diǎn)的距離這種屬性)。
要想成為一個(gè)描述符,一個(gè)類必須具有實(shí)現(xiàn) __get__, __set__,和 __delete__ 三個(gè)方法中至少一個(gè)。
- __get__(self, instance, owner)
定義當(dāng)試圖取出描述符的值時(shí)的行為。instance是擁有者類的實(shí)例,owner是擁有者類本身。 - __set__(self, instance, value)
定義當(dāng)描述符的值改變時(shí)的行為。instance 是擁有者類的實(shí)例,value 是要付給描述符的值。 - __delete__(self, instance, owner)
定義當(dāng)描述符得知被刪除時(shí)的行為。instance 是擁有者類的實(shí)例。
看一個(gè)描述符的有效應(yīng)用,單位轉(zhuǎn)換:
class Meter(object):
''' 米的描述符'''
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Foot(object):
"""英尺的描述符"""
def __get__(self, instance, owner):
return instance.meter * 3.2808
def __set__(self, instance, value):
instance.meter = float(value) / 3.2808
class Distance(object):
"""用于描述距離的類,包含英尺和米兩個(gè)描述符"""
meter = Meter()
foot = Foot()
>>> dis = Distance()
>>> dis.meter = 10
>>> dis.meter
10.0
>>> dis.foot
32.808
拷貝
當(dāng)拷貝一個(gè)對象,改變這個(gè)對象而不影響原有的對象。這時(shí)就需要用到 Python 的 copy 模塊了。Python 模塊并不具有感知能力,我們需要告訴 Python 如何有效率的拷貝對象。
- __copy__(self)
定義對類的實(shí)例使用copy.copy()時(shí)的行為。copy.copy()返回一個(gè)對象的淺拷貝,這意味著拷貝出的實(shí)例是全新的,然而里面的數(shù)據(jù)全都是引用的。也就是說,對象本身是拷貝的,但是它的數(shù)據(jù)還是引用的(所以淺拷貝中的數(shù)據(jù)更改會影響原對象)。 - __deepcopy__(self, memodict)
定義對類的實(shí)例使用copy.deepcopy()時(shí)的行為。copy.deepcopy()返回一個(gè)對象的深拷貝,這個(gè)對象和他的數(shù)據(jù)全都被拷貝了一份。memodict是一個(gè)先前拷貝對象的緩存,它優(yōu)化了拷貝過程,而且可以防止拷貝遞歸數(shù)據(jù)結(jié)構(gòu)是產(chǎn)生無限遞歸。當(dāng)你想深拷貝一個(gè)單獨(dú)的屬性時(shí),在那個(gè)屬性上調(diào)用copy.deepcopy(),使用memodict作為第一個(gè)參數(shù)。
這些魔法方法有什么用呢?像往常一樣,當(dāng)你需要比默認(rèn)行為更加精確的控制時(shí)。
Pickling
Pickling 是 Python 數(shù)據(jù)結(jié)構(gòu)的序列化過程,當(dāng)你想存儲一個(gè)對象稍后再取出讀取時(shí),Pickling 會顯得十分有用。然而它同樣也是擔(dān)憂和混淆的主要來源。
Pickling 是如此的重要,以至于它不僅僅有自己的模塊(pickle),還有自己的協(xié)議和魔法方法。首先,我們先來簡要的介紹一下如何 pickle 已存在的對象類型。
Pickling:A Quick Soak in the Brine
假設(shè)你有一個(gè)字典,你想存儲它,稍后再取出來。你可以把它的內(nèi)容寫入一個(gè)文件,小心翼翼地確保使用了正確地格式,要把它讀取出來,你可以使用 exec() 或處理文件輸入。但是這種方法并不可靠:如果你使用純文本來存儲重要數(shù)據(jù),數(shù)據(jù)很容易以多種方式被破壞或者修改,導(dǎo)致你的程序崩潰,更糟糕的情況下,還可能在你的計(jì)算機(jī)上運(yùn)行惡意代碼。因此,我們要 pickle 它:
import pickle
data = {
'foo': [1, 2, 3],
'bar': ('Hello', 'world'),
'baz': True
}
jar = open('data.pkl', 'wb')
pickle.dump(data. jar) # 將 pickle 后的數(shù)據(jù)寫入 jar 文件
jar.close()
過了幾個(gè)小時(shí),我們想把它取出來,我們只需要反 pickle 它:
import pickle
pkl_file = open('data.pkl', 'rb') # 與 pickle 后的數(shù)據(jù)連接
data = pickle.load(pkl_file) # 把它加載進(jìn)一個(gè)變量
print data
pkl_file.close()
它就是我們之前的 data 。
現(xiàn)在,還需要謹(jǐn)慎地說一句: pickle 并不完美。Pickle 文件很容易因?yàn)槭鹿驶虮还室獾钠茐牡?。Pickling 或許比純文本文件安全一些,但是依然有可能被用來運(yùn)行惡意代碼。而且它還不支持跨 Python 版本,所以不要指望分發(fā)pickle對象之后所有人都能正確地讀取。然而不管怎么樣,它依然是一個(gè)強(qiáng)有力的工具,可以用于緩存和其他類型的持久化工作。
Pickle 你的對象
Pickle 不僅僅可以用于內(nèi)建類型,任何遵守 pickle 協(xié)議的類都可以被 pickle。Pickle 協(xié)議有四個(gè)可選方法,可以讓類自定義他們的行為。
- __getinitargs__(self)
如果你想讓你的類在反 pickle 時(shí)調(diào)用__init__,你可以定義__getinitargs__(self),它會返回一個(gè)參數(shù)元組,這個(gè)元組會傳遞給__init__。注意,這個(gè)方法只能用于舊式類。 - __getnewargs__(self)
對新式類來說,可以通過這個(gè)方法改變類在 pickle 時(shí)傳遞給__new__的參數(shù)。這個(gè)方法應(yīng)該返回一個(gè)參數(shù)元組。 - __getstate__(self)
可以自定義對象被 pickle 時(shí)被存儲的狀態(tài),而不是用對象的__dict__屬性。這個(gè)狀態(tài)在對象被反 pickle 時(shí)會被__setstate__使用。 - __setstate__(self)
當(dāng)一個(gè)對象被反 pickle 時(shí),如果定義了__setstate__,對象的狀態(tài)會傳遞給這個(gè)魔法方法,而不是直接應(yīng)用到對象的__dict__屬性。這個(gè)魔法方法和__get__相互依存:當(dāng)這兩個(gè)方法都被定義時(shí),你可以在 pickle 時(shí)使用任何方法保存對象的任何狀態(tài)。 - __reduce__(self)
當(dāng)定義擴(kuò)展類型時(shí)(也就是使用 Python 的 C 語言 API 實(shí)現(xiàn)的類型),如果你想 pickle 它們,你必須告訴 Python 如何 pickle 它們。__reduce__被定義之后,當(dāng)對象被 pickle 時(shí)就會被調(diào)用。他要么返回一個(gè)代表全局名稱的字符串,Python 會查找它并 pickle,要么返回一個(gè)元組。這個(gè)元組包含 2 到 5 個(gè)元素,其中包括一個(gè)可調(diào)用的對象,用于重建對象時(shí)調(diào)用;一個(gè)參數(shù)元素,供那個(gè)可調(diào)用對象使用;被傳遞給__setstate__的狀態(tài)(可選);一個(gè)產(chǎn)生被 pickle 的列表元素的迭代器(可選);一個(gè)產(chǎn)生被 pickle 的字典元素的迭代器(可選); - __reduce_ex__(self)
__reduce_ex__的存在是為了兼容性。如果它被定義,在 pickle 時(shí)__reduce_ex__會代替__reduce__被調(diào)用。__reduce__也可以被定義,用于不支持__reduce_ex__的舊版 pickle 的 API 調(diào)用。
一個(gè)例子
例子是 Slate,它會記住它的值曾經(jīng)是什么,以及那些值是什么時(shí)候賦給他的。然而每次被 pickle 時(shí)它都會變成空白,因?yàn)楫?dāng)前的值不會被存儲:
import time
class Slate:
'''存儲一個(gè)字符串和一個(gè)變更日志的類
每次被 pickle 都會忘記它當(dāng)前的值
'''
def __init__(self, value):
self.value = value
self.last_change = time.asctime()
self.history = {}
def change(self, new_value):
# 改變當(dāng)前值,將上一個(gè)值就到歷史
self.history[self.last_change] = self.value
self.value = new_value
self.last_change = time.asctime()
def print_change(self):
print('Change log for Slate object:')
for k, v in self.history.items():
print('{}\t{}'.format()k, v)
def __getstate__(self):
# 故意不返回 self.value 或 self.last_change
# 我們想在反 pickle 時(shí)得到一個(gè)空白的 slate
return self.history
def __setstate__(self):
# 使self.history = slate
# value 和 last_change 為定義
self.history = state
self.value, self.last_change = None, None
總結(jié)
這本指南的目標(biāo)是使所有閱讀它的人都能有所收獲,無論他們有沒有使用 Python 或者進(jìn)行面向?qū)ο缶幊痰慕?jīng)驗(yàn)。如果你剛剛開始學(xué)習(xí)Python,你會得到寶貴的基礎(chǔ)知識,了解如何寫出具有豐富特性的,優(yōu)雅而且易用的類。如果你是中級的 Python 程序員,你或許能掌握一些新的概念和技巧,以及一些可以減少代碼行數(shù)的好辦法。如果你是專家級別的 Python 愛好者,你又重新復(fù)習(xí)了一遍某些可能已經(jīng)忘掉的知識,也可能順便了解了一些新技巧。無論你的水平怎樣,我希望這趟遨游 Python 特殊方法的旅行,真的對你產(chǎn)生了魔法般的效果(實(shí)在忍不住不說最后這個(gè)雙關(guān))。
附錄1:如何調(diào)用魔法方法
一些魔法方法直接和內(nèi)建函數(shù)對應(yīng),這種情況下,如何調(diào)用它們是顯而易見的。然而,另外的情況下,調(diào)用魔法方法的途徑并不是那么明顯。這個(gè)附錄旨在展示那些不那么明顯的調(diào)用魔法方法的語法。
| 魔法方法 | 什么時(shí)候被調(diào)用 | 解釋 |
|---|---|---|
__new__(cls [,...]) |
instance = MaClass(arg1, arg2) |
__new__ 在實(shí)例創(chuàng)建時(shí)調(diào)用 |
__init__(self[,...]) |
instance = MyClass(arg1, arg2) |
__init__ 在實(shí)例創(chuàng)建時(shí)調(diào)用 |
__cmp__(self) |
self == other, self > other 等 |
進(jìn)行比較時(shí)調(diào)用 |
__pos__(self) |
+self |
一元加法符號 |
__neg__(self) |
-self |
一元減法符號 |
__invert__(self) |
~self |
按位取反 |
__index__(self) |
x[self] |
當(dāng)對象用于索引時(shí) |
__nonzero__(self) |
bool(self |
對象的布爾值 |
__getattr__(self, name) |
self.name # name 不存在 |
訪問不存在的屬性 |
__setattr__(self, name) |
self.name = val |
給屬性賦值 |
__delattr__(self, name) |
def self.name |
刪除屬性 |
__getattribute(self, name) |
self.name |
訪問任意屬性 |
__getitem__(self, key) |
self[key] |
使用索引給某個(gè)元素賦值 |
__setitem__(self, key) |
self[key] = val |
使用索引給某個(gè)元素賦值 |
__delitem__(self, key) |
del self[key] |
使用索引刪除某個(gè)對象 |
iter(self) |
for x in self |
迭代 |
__contains__(self, value) |
value in self, value not in self |
使用 in 進(jìn)行成員測試 |
__call__(self [,...] |
self(args) |
“調(diào)用”一個(gè)實(shí)例 |
__enter__(self) |
with self as x: |
with 聲明的上下文管理器 |
__exit__(self, exc, val, trace) |
with self as x |
with 聲明的上下文管理器 |
__getstate__(self) |
pickle.dump(pkl_file, self) |
Pickling |
__setstate(self) |
data = pickle.load(pkl_file) |
Pickling |
Python 3 中的變化
記錄幾個(gè)在對象模型方面 Python 3 和 Python 2.x 之間的主要區(qū)別。
Python 3 中 string 和 unicode 的區(qū)別不復(fù)存在,因此 __unicode__ 被取消了, __bytes__ 加入進(jìn)來(與 Python 2.7 中的 __str__ 和 __unicode__ 行為類似),用于新的創(chuàng)建字節(jié)數(shù)組的內(nèi)建方法。
Python 3 中默認(rèn)除法變成了 true 除法,因此 __div__ 被取消了。
__coerce__ 被取消了,因?yàn)楹推渌Хǚ椒ㄓ泄δ苌系闹貜?fù),以及本身行為令人迷惑。
__cmp__ 被取消了,因?yàn)楹推渌Хǚ椒ㄓ泄δ苌系闹貜?fù)。
__nonzero__ 被重命名成 __bool__。