轉(zhuǎn)自:http://pyzh.readthedocs.io/en/latest/python-magic-methods-guide.html
?
?
原文: | http://www.rafekettler.com/magicmethods.html |
---|---|
原作者: | Rafe Kettler |
翻譯: | hit9 |
原版(英文版) Repo: | https://github.com/RafeKettler/magicmethods |
?
Contents
-
(譯)Python魔法方法指南
11.1. 簡(jiǎn)介
本指南歸納于我的幾個(gè)月的博客,主題是? 魔法方法 ?。
什么是魔法方法呢?它們?cè)诿嫦驅(qū)ο蟮腜ython的處處皆是。它們是一些可以讓你對(duì)類添加“魔法”的特殊方法。 它們經(jīng)常是兩個(gè)下劃線包圍來命名的(比如?__init__,?__lt__?)。但是現(xiàn)在沒有很好的文檔來解釋它們。 所有的魔法方法都會(huì)在Python的官方文檔中找到,但是它們組織松散。而且很少會(huì)有示例(有的是無聊的語法描述, 語言參考)。
所以,為了修復(fù)我感知的Python文檔的缺陷,我開始提供更為通俗的,有示例支持的Python魔法方法指南。我一開始 寫了一些博文,現(xiàn)在我把這些博文總起來成為一篇指南。
希望你喜歡這篇指南,一篇友好,通俗易懂的Python魔法方法指南!
11.2. 構(gòu)造方法
我們最為熟知的基本的魔法方法就是?__init__?,我們可以用它來指明一個(gè)對(duì)象初始化的行為。然而,當(dāng)我們調(diào)用?x = SomeClass()?的時(shí)候,?__init__?并不是第一個(gè)被調(diào)用的方法。事實(shí)上,第一個(gè)被調(diào)用的是?__new__?,這個(gè) 方法才真正地創(chuàng)建了實(shí)例。當(dāng)這個(gè)對(duì)象的生命周期結(jié)束的時(shí)候,?__del__?會(huì)被調(diào)用。讓我們近一步理解這三個(gè)方法:
-
__new__(cls,[...)
__new__?是對(duì)象實(shí)例化時(shí)第一個(gè)調(diào)用的方法,它只取下?cls?參數(shù),并把其他參數(shù)傳給?__init__?。?__new__?很少使用,但是也有它適合的場(chǎng)景,尤其是當(dāng)類繼承自一個(gè)像元組或者字符串這樣不經(jīng)常改變的類型的時(shí)候。我不打算深入討論__new__?,因?yàn)樗⒉皇呛苡杏茫? Python文檔 ?中 有詳細(xì)的說明。
-
__init__(self,[...])
類的初始化方法。它獲取任何傳給構(gòu)造器的參數(shù)(比如我們調(diào)用?x = SomeClass(10, ‘foo’)?,?__init__?就會(huì)接到參數(shù)?10?和?‘foo’?。?__init__在Python的類定義中用的最多。
-
__del__(self)
__new__?和?__init__?是對(duì)象的構(gòu)造器,?__del__?是對(duì)象的銷毀器。它并非實(shí)現(xiàn)了語句?del x?(因此該語句不等同于?x.__del__())。而是定義了當(dāng)對(duì)象被垃圾回收時(shí)的行為。 當(dāng)對(duì)象需要在銷毀時(shí)做一些處理的時(shí)候這個(gè)方法很有用,比如?socket?對(duì)象、文件對(duì)象。但是需要注意的是,當(dāng)Python解釋器退出但對(duì)象仍然存活的時(shí)候,?__del__?并不會(huì) 執(zhí)行。 所以養(yǎng)成一個(gè)手工清理的好習(xí)慣是很重要的,比如及時(shí)關(guān)閉連接。
這里有個(gè)?__init__?和?__del__?的例子:
from os.path import join class FileObject: '''文件對(duì)象的裝飾類,用來保證文件被刪除時(shí)能夠正確關(guān)閉。''' def __init__(self, filepath='~', filename='sample.txt'): # 使用讀寫模式打開filepath中的filename文件 self.file = open(join(filepath, filename), 'r+') def __del__(self): self.file.close() del self.file
11.3. 操作符
使用Python魔法方法的一個(gè)巨大優(yōu)勢(shì)就是可以構(gòu)建一個(gè)擁有Python內(nèi)置類型行為的對(duì)象。這意味著你可以避免使用非標(biāo)準(zhǔn)的、丑陋的方式來表達(dá)簡(jiǎn)單的操作。 在一些語言中,這樣做很常見:
if instance.equals(other_instance): # do something
你當(dāng)然可以在Python也這么做,但是這樣做讓代碼變得冗長(zhǎng)而混亂。不同的類庫可能對(duì)同一種比較操作采用不同的方法名稱,這讓使用者需要做很多沒有必要的工作。運(yùn)用魔法方法的魔力,我們可以定義方法?__eq__
if instance == other_instance: # do something
這是魔法力量的一部分,這樣我們就可以創(chuàng)建一個(gè)像內(nèi)建類型那樣的對(duì)象了!
11.3.1. 比較操作符
Python包含了一系列的魔法方法,用于實(shí)現(xiàn)對(duì)象之間直接比較,而不需要采用方法調(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?時(shí)返回一個(gè)負(fù)整數(shù),在self == other?時(shí)返回0,在?self > other?時(shí)返回正整數(shù)。最好只定義你所需要的比較形式,而不是一次定義全部。 如果你需要實(shí)現(xiàn)所有的比較形式,而且它們的判斷標(biāo)準(zhǔn)類似,那么?__cmp__?是一個(gè)很好的方法,可以減少代碼重復(fù),讓代碼更簡(jiǎn)潔。
-
__eq__`(self, other)
定義等于操作符(==)的行為。
-
__ne__(self, other)
定義不等于操作符(!=)的行為。
-
__lt__(self, other)
定義小于操作符(<)的行為。
-
__gt__(self, other)
定義大于操作符(>)的行為。
-
__le__(self, other)
定義小于等于操作符(<)的行為。
-
__ge__(self, other)
定義大于等于操作符(>)的行為。
舉個(gè)例子,假如我們想用一個(gè)類來存儲(chǔ)單詞。我們可能想按照字典序(字母順序)來比較單詞,字符串的默認(rèn)比較行為就是這樣。我們可能也想按照其他規(guī)則來比較字符串,像是長(zhǎng)度,或者音節(jié)的數(shù)量。在這個(gè)例子中,我們使用長(zhǎng)度作為比較標(biāo)準(zhǔn),下面是一種實(shí)現(xiàn):
class Word(str): '''單詞類,按照單詞長(zhǎng)度來定義比較行為''' def __new__(cls, word): # 注意,我們只能使用 __new__ ,因?yàn)閟tr是不可變類型 # 所以我們必須提前初始化它(在實(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?對(duì)象(?Word(‘foo’)?和?Word(‘bar’))然后根據(jù)長(zhǎng)度來比較它們。注意我們沒有定義?__eq__?和?__ne__?,這是因?yàn)橛袝r(shí)候它們會(huì)導(dǎo)致奇怪的結(jié)果(很明顯,?Word(‘foo’) == Word(‘bar’)?得到的結(jié)果會(huì)是true)。根據(jù)長(zhǎng)度測(cè)試是否相等毫無意義,所以我們使用?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?放在類的定義之上就可以了
11.3.2. 數(shù)值操作符
就像你可以使用比較操作符來比較類的實(shí)例,你也可以定義數(shù)值操作符的行為。固定好你的安全帶,這樣的操作符真的有很多。看在組織的份上,我把它們分成了五類:一元操作符,常見算數(shù)操作符,反射算數(shù)操作符(后面會(huì)涉及更多),增強(qiáng)賦值操作符,和類型轉(zhuǎn)換操作符。
11.3.2.1. 一元操作符
一元操作符只有一個(gè)操作符。
-
__pos__(self)
實(shí)現(xiàn)取正操作,例如?+some_object。
-
__neg__(self)
實(shí)現(xiàn)取負(fù)操作,例如?-some_object。
-
__abs__(self)
實(shí)現(xiàn)內(nèi)建絕對(duì)值函數(shù)?abs()?操作。
-
__invert__(self)
實(shí)現(xiàn)取反操作符?~。
-
__round__(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ù)。
11.3.2.2. 常見算數(shù)操作符
現(xiàn)在,我們來看看常見的二元操作符(和一些函數(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)算符?^?。
11.3.2.3. 反射算數(shù)運(yùn)算符
還記得剛才我說會(huì)談到反射運(yùn)算符嗎?可能你會(huì)覺得它是什么高端霸氣上檔次的概念,其實(shí)這東西挺簡(jiǎn)單的,下面舉個(gè)例子:
some_object + other
這是“常見”的加法,反射是一樣的意思,只不過是運(yùn)算符交換了一下位置:
other + some_object
所有反射運(yùn)算符魔法方法和它們的常見版本做的工作相同,只不過是處理交換連個(gè)操作數(shù)之后的情況。絕大多數(shù)情況下,反射運(yùn)算和正常順序產(chǎn)生的結(jié)果是相同的,所以很可能你定義?__radd__?時(shí)只是調(diào)用一下?__add__。注意一點(diǎn),操作符左側(cè)的對(duì)象(也就是上面的?other?)一定不要定義(或者產(chǎn)生?NotImplemented?異常) 操作符的非反射版本。例如,在上面的例子中,只有當(dāng)?other?沒有定義?__add__?時(shí)some_object.__radd__?才會(huì)被調(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)使用?/?操作符的反射除法。
-
__rtruediv__(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)算符?<<?的作用。
-
__rshift__(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)算符?^?。
11.3.2.4. 增強(qiáng)賦值運(yùn)算符
Python同樣提供了大量的魔法方法,可以用來自定義增強(qiáng)賦值操作的行為。或許你已經(jīng)了解增強(qiáng)賦值,它融合了“常見”的操作符和賦值操作,如果你還是沒聽明白,看下面的例子:
x = 5 x += 1 # 也就是 x = x + 1
這些方法都應(yīng)該返回左側(cè)操作數(shù)應(yīng)該被賦予的值(例如,?a += b?__iadd__?也許會(huì)返回?a + b?,這個(gè)結(jié)果會(huì)被賦給 a ),下面是方法列表:
-
__iadd__(self, other)
實(shí)現(xiàn)加法賦值操作。
-
__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)使用?/=?操作符的除法賦值操作。
-
__itruediv__(self, other)
實(shí)現(xiàn)?_true_?除法賦值操作,這個(gè)函數(shù)只有使用?from __future__ import division?時(shí)才有作用。
-
__imod__(self, other)
實(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)算符?^=?。
11.3.2.5. 類型轉(zhuǎ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)換。
-
__oct__(self)
實(shí)現(xiàn)到八進(jìn)制數(shù)的類型轉(zhuǎn)換。
-
__hex__(self)
實(shí)現(xiàn)到十六進(jìn)制數(shù)的類型轉(zhuǎn)換。
-
__index__(self)
實(shí)現(xiàn)當(dāng)對(duì)象用于切片表達(dá)式時(shí)到一個(gè)整數(shù)的類型轉(zhuǎn)換。如果你定義了一個(gè)可能會(huì)用于切片操作的數(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)換成相同的類型。
11.4. 類的表示
使用字符串來表示類是一個(gè)相當(dāng)有用的特性。在Python中有一些內(nèi)建方法可以返回類的表示,相對(duì)應(yīng)的,也有一系列魔法方法可以用來自定義在使用這些內(nèi)建函數(shù)時(shí)類的行為。
-
__str__(self)
定義對(duì)類的實(shí)例調(diào)用?str()?時(shí)的行為。
-
__repr__(self)
定義對(duì)類的實(shí)例調(diào)用?repr()?時(shí)的行為。?str()?和?repr()?最主要的差別在于“目標(biāo)用戶”。?repr()?的作用是產(chǎn)生機(jī)器可讀的輸出(大部分情況下,其輸出可以作為有效的Python代碼),而?str()?則產(chǎn)生人類可讀的輸出。
-
__unicode__(self)
定義對(duì)類的實(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, 0:abc!”.format(a)?會(huì)導(dǎo)致調(diào)用?a.__format__(“abc”)?。當(dāng)定義你自己的數(shù)值類型或字符串類型時(shí),你可能想提供某些特殊的格式化選項(xiàng),這種情況下這個(gè)魔法方法會(huì)非常有用。
-
__hash__(self)
定義對(duì)類的實(shí)例調(diào)用?hash()?時(shí)的行為。它必須返回一個(gè)整數(shù),其結(jié)果會(huì)被用于字典中鍵的快速比較。同時(shí)注意一點(diǎn),實(shí)現(xiàn)這個(gè)魔法方法通常也需要實(shí)現(xiàn)__eq__?,并且遵守如下的規(guī)則:?a == b?意味著?hash(a) == hash(b)。
-
__nonzero__(self)
定義對(duì)類的實(shí)例調(diào)用?bool()?時(shí)的行為,根據(jù)你自己對(duì)類的設(shè)計(jì),針對(duì)不同的實(shí)例,這個(gè)魔法方法應(yīng)該相應(yīng)地返回True或False。
-
__dir__(self)
定義對(duì)類的實(shí)例調(diào)用?dir()?時(shí)的行為,這個(gè)方法應(yīng)該向調(diào)用者返回一個(gè)屬性列表。一般來說,沒必要自己實(shí)現(xiàn)?__dir__?。但是如果你重定義了?__getattr__或者?__getattribute__?(下個(gè)部分會(huì)介紹),乃至使用動(dòng)態(tài)生成的屬性,以實(shí)現(xiàn)類的交互式使用,那么這個(gè)魔法方法是必不可少的。
到這里,我們基本上已經(jīng)結(jié)束了魔法方法指南中無聊并且例子匱乏的部分。既然我們已經(jīng)介紹了較為基礎(chǔ)的魔法方法,是時(shí)候涉及更高級(jí)的內(nèi)容了。
11.5. 訪問控制
很多從其他語言轉(zhuǎn)向Python的人都抱怨Python的類缺少真正意義上的封裝(即沒辦法定義私有屬性然后使用公有的getter和setter)。然而事實(shí)并非如此。實(shí)際上Python不是通過顯式定義的字段和方法修改器,而是通過魔法方法實(shí)現(xiàn)了一系列的封裝。
- __getattr__(self, name)
當(dāng)用戶試圖訪問一個(gè)根本不存在(或者暫時(shí)不存在)的屬性時(shí),你可以通過這個(gè)魔法方法來定義類的行為。這個(gè)可以用于捕捉錯(cuò)誤的拼寫并且給出指引,使用廢棄屬性時(shí)給出警告(如果你愿意,仍然可以計(jì)算并且返回該屬性),以及靈活地處理AttributeError。只有當(dāng)試圖訪問不存在的屬性時(shí)它才會(huì)被調(diào)用,所以這不能算是一個(gè)真正的封裝的辦法。
- __setattr__(self, name, value)
和?__getattr__?不同,?__setattr__?可以用于真正意義上的封裝。它允許你自定義某個(gè)屬性的賦值行為,不管這個(gè)屬性存在與否,也就是說你可以對(duì)任意屬性的任何變化都定義自己的規(guī)則。然后,一定要小心使用?__setattr__?,這個(gè)列表最后的例子中會(huì)有所展示。
- __delattr__(self, name)
這個(gè)魔法方法和?__setattr__?幾乎相同,只不過它是用于處理刪除屬性時(shí)的行為。和?_setattr__?一樣,使用它時(shí)也需要多加小心,防止產(chǎn)生無限遞歸(在__delattr__?的實(shí)現(xiàn)中調(diào)用?del self.name?會(huì)導(dǎo)致無限遞歸)。
- __getattribute__(self, name)
` __getattribute__` 看起來和上面那些方法很合得來,但是最好不要使用它。__getattribute__?只能用于新式類。在最新版的Python中所有的類都是新式類,在老版Python中你可以通過繼承?object?來創(chuàng)建新式類。?__getattribute__?允許你自定義屬性被訪問時(shí)的行為,它也同樣可能遇到無限遞歸問題(通過調(diào)用基類的__getattribute__?來避免)。?__getattribute__?基本上可以替代?__getattr__?。只有當(dāng)它被實(shí)現(xiàn),并且顯式地被調(diào)用,或者產(chǎn)生?AttributeError?時(shí)它才被使用。 這個(gè)魔法方法可以被使用(畢竟,選擇權(quán)在你自己),我不推薦你使用它,因?yàn)樗氖褂梅秶鄬?duì)有限(通常我們想要在賦值時(shí)進(jìn)行特殊操作,而不是取值時(shí)),而且實(shí)現(xiàn)這個(gè)方法很容易出現(xiàn)Bug。
自定義這些控制屬性訪問的魔法方法很容易導(dǎo)致問題,考慮下面這個(gè)例子:
def __setattr__(self, name. value): self.name = value # 因?yàn)槊看螌傩苑刀家{(diào)用 __setattr__(),所以這里的實(shí)現(xiàn)會(huì)導(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é)任越大,了解如何正確的使用魔法方法更加重要。
到這里,我們對(duì)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ù)器的類 每次值的變化都會(huì)導(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)
11.6. 自定義序列
有許多辦法可以讓你的Python類表現(xiàn)得像是內(nèi)建序列類型(字典,元組,列表,字符串等)。這些魔法方式是目前為止我最喜歡的。它們給了你難以置信的控制能力,可以讓你的類與一系列的全局函數(shù)完美結(jié)合。在了解激動(dòng)人心的內(nèi)容之前,首先你需要掌握一些預(yù)備知識(shí)。
11.6.1. 預(yù)備知識(shí)
既然講到創(chuàng)建自己的序列類型,就不得不說一說協(xié)議了。協(xié)議類似某些語言中的接口,里面包含的是一些必須實(shí)現(xiàn)的方法。在Python中,協(xié)議完全是非正式的,也不需要顯式的聲明,事實(shí)上,它們更像是一種參考標(biāo)準(zhǔn)。
為什么我們要講協(xié)議?因?yàn)樵赑ython中實(shí)現(xiàn)自定義容器類型需要用到一些協(xié)議。首先,不可變?nèi)萜黝愋陀腥缦聟f(xié)議:想實(shí)現(xiàn)一個(gè)不可變?nèi)萜鳎阈枰x?__len__?和__getitem__?(后面會(huì)具體說明)。可變?nèi)萜鞯膮f(xié)議除了上面提到的兩個(gè)方法之外,還需要定義?__setitem__?和?__delitem__?。最后,如果你想讓你的對(duì)象可以迭代,你需要定義?__iter__?,這個(gè)方法返回一個(gè)迭代器。迭代器必須遵守迭代器協(xié)議,需要定義?__iter__?(返回它自己)和?next?方法。
11.6.2. 容器背后的魔法方法
-
__len__(self)
返回容器的長(zhǎng)度,可變和不可變類型都需要實(shí)現(xiàn)。
-
__getitem__(self, key)
定義對(duì)容器中某一項(xiàng)使用?self[key]?的方式進(jìn)行讀取操作時(shí)的行為。這也是可變和不可變?nèi)萜黝愋投夹枰獙?shí)現(xiàn)的一個(gè)方法。它應(yīng)該在鍵的類型錯(cuò)誤式產(chǎn)生TypeError?異常,同時(shí)在沒有與鍵值相匹配的內(nèi)容時(shí)產(chǎn)生?KeyError?異常。
-
__setitem__(self, key)
定義對(duì)容器中某一項(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)
定義了對(duì)容器使用?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)行成員測(cè)試時(shí)類的行為。你可能好奇為什么這個(gè)方法不是序列協(xié)議的一部分,原因是,如果?__contains__?沒有定義,Python就會(huì)迭代整個(gè)序列,如果找到了需要的一項(xiàng)就返回?True?。
-
__missing__(self ,key)
__missing__?在字典的子類中使用,它定義了當(dāng)試圖訪問一個(gè)字典中不存在的鍵時(shí)的行為(目前為止是指字典的實(shí)例,例如我有一個(gè)字典?d?,?“george”?不是字典中的一個(gè)鍵,當(dāng)試圖訪問?d[“george’]?時(shí)就會(huì)調(diào)用d.__missing__(“george”)?)。
11.6.3. 一個(gè)例子
讓我們來看一個(gè)實(shí)現(xiàn)了一些函數(shù)式結(jié)構(gòu)的列表,可能在其他語言中這種結(jié)構(gòu)更常見(例如Haskell):
class FunctionalList: '''一個(gè)列表的封裝類,實(shí)現(xiàn)了一些額外的函數(shù)式 方法,例如head, tail, init, last, drop和take。''' def __init__(self, values=None): if values is None: self.values = [] else: self.values = values def __len__(self): return len(self.values) def __getitem__(self, key): # 如果鍵的類型或值不合法,列表會(huì)返回異常 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.valuse[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]
就是這些,一個(gè)(微不足道的)有用的例子,向你展示了如何實(shí)現(xiàn)自己的序列。當(dāng)然啦,自定義序列有更大的用處,而且絕大部分都在標(biāo)準(zhǔn)庫中實(shí)現(xiàn)了(Python是自帶電池的,記得嗎?),像?Counter?,?OrderedDict?和?NamedTuple?。
11.7. 反射
你可以通過定義魔法方法來控制用于反射的內(nèi)建函數(shù)?isinstance?和?issubclass?的行為。下面是對(duì)應(yīng)的魔法方法:
-
__instancecheck__(self, instance)
檢查一個(gè)實(shí)例是否是你定義的類的一個(gè)實(shí)例(例如?isinstance(instance, class)?)。
-
__subclasscheck__(self, subclass)
檢查一個(gè)類是否是你定義的類的子類(例如?issubclass(subclass, class))。
這幾個(gè)魔法方法的適用范圍看起來有些窄,事實(shí)也正是如此。我不會(huì)在反射魔法方法上花費(fèi)太多時(shí)間,因?yàn)橄啾绕渌Хǚ椒ㄋ鼈冿@得不是很重要。但是它們展示了在Python中進(jìn)行面向?qū)ο缶幊蹋ɑ蛘呖傮w上使用Python進(jìn)行編程)時(shí)很重要的一點(diǎn):不管做什么事情,都會(huì)有一個(gè)簡(jiǎn)單方法,不管它常用不常用。這些魔法方法可能看起來沒那么有用,但是當(dāng)你真正需要用到它們的時(shí)候,你會(huì)感到很幸運(yùn),因?yàn)樗鼈冞€在那兒(也因?yàn)槟汩喿x了這本指南!)
11.8. 抽象基類
請(qǐng)參考?http://docs.python.org/2/library/abc.html.
11.9. 可調(diào)用的對(duì)象
你可能已經(jīng)知道了,在Python中,函數(shù)是一等的對(duì)象。這意味著它們可以像其他任何對(duì)象一樣被傳遞到函數(shù)和方法中,這是一個(gè)十分強(qiáng)大的特性。
Python中一個(gè)特殊的魔法方法允許你自己類的對(duì)象表現(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í)體的位置''' 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
11.10. 上下文管理器
在Python 2.5中引入了一個(gè)全新的關(guān)鍵詞,隨之而來的是一種新的代碼復(fù)用方法——with?聲明。上下文管理的概念在Python中并不是全新引入的(之前它作為標(biāo)準(zhǔn)庫的一部分實(shí)現(xiàn)),直到PEP 343被接受,它才成為一種一級(jí)的語言結(jié)構(gòu)。可能你已經(jīng)見過這種寫法了:
with open('foo.txt') as bar: # 使用bar進(jìn)行某些操作
當(dāng)對(duì)象使用?with?聲明創(chuàng)建時(shí),上下文管理器允許類做一些設(shè)置和清理工作。上下文管理器的行為由下面兩個(gè)魔法方法所定義:
-
__enter__(self)
定義使用?with?聲明創(chuàng)建的語句塊最開始上下文管理器應(yīng)該做些什么。注意__enter__?的返回值會(huì)賦給?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?會(huì)是None?。否則,你可以選擇處理這個(gè)異常或者讓用戶來處理。如果你想處理異常,確保?__exit__?在完成工作之后返回?True?。如果你不想處理異常,那就讓它發(fā)生吧。
對(duì)一些具有良好定義的且通用的設(shè)置和清理行為的類,__enter__?和?__exit__?會(huì)顯得特別有用。你也可以使用這幾個(gè)方法來創(chuàng)建通用的上下文管理器,用來包裝其他對(duì)象。下面是一個(gè)例子:
class Closer: '''一個(gè)上下文管理器,可以在with語句中 使用close()自動(dòng)關(guān)閉對(duì)象''' 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 closable.' 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() ... # 為了簡(jiǎn)單,省略了某些輸出 >>> conn.dir() # 很長(zhǎng)的 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)對(duì)象沒有close()方法時(shí)的處理)。
11.11. 創(chuàng)建描述符對(duì)象
描述符是一個(gè)類,當(dāng)使用取值,賦值和刪除 時(shí)它可以改變其他對(duì)象。描述符不是用來單獨(dú)使用的,它們需要被一個(gè)擁有者類所包含。描述符可以用來創(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, owner)
定義當(dāng)描述符的值改變時(shí)的行為。?instance?是擁有者類的實(shí)例,?value?是要賦給描述符的值。
-
__delete__(self, instance, owner)
定義當(dāng)描述符的值被刪除時(shí)的行為。?instance?是擁有者類的實(shí)例
現(xiàn)在,來看一個(gè)描述符的有效應(yīng)用:?jiǎn)挝晦D(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, owner): 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()
11.12. 拷貝
有些時(shí)候,特別是處理可變對(duì)象時(shí),你可能想拷貝一個(gè)對(duì)象,改變這個(gè)對(duì)象而不影響原有的對(duì)象。這時(shí)就需要用到Python的?copy?模塊了。然而(幸運(yùn)的是),Python模塊并不具有感知能力, 因此我們不用擔(dān)心某天基于Linux的機(jī)器人崛起。但是我們的確需要告訴Python如何有效率地拷貝對(duì)象。
-
__copy__(self)
定義對(duì)類的實(shí)例使用?copy.copy()?時(shí)的行為。?copy.copy()?返回一個(gè)對(duì)象的淺拷貝,這意味著拷貝出的實(shí)例是全新的,然而里面的數(shù)據(jù)全都是引用的。也就是說,對(duì)象本身是拷貝的,但是它的數(shù)據(jù)還是引用的(所以淺拷貝中的數(shù)據(jù)更改會(huì)影響原對(duì)象)。
-
__deepcopy__(self, memodict=)
定義對(duì)類的實(shí)例使用?copy.deepcopy()?時(shí)的行為。?copy.deepcopy()?返回一個(gè)對(duì)象的深拷貝,這個(gè)對(duì)象和它的數(shù)據(jù)全都被拷貝了一份。?memodict?是一個(gè)先前拷貝對(duì)象的緩存,它優(yōu)化了拷貝過程,而且可以防止拷貝遞歸數(shù)據(jù)結(jié)構(gòu)時(shí)產(chǎn)生無限遞歸。當(dāng)你想深拷貝一個(gè)單獨(dú)的屬性時(shí),在那個(gè)屬性上調(diào)用?copy.deepcopy(),使用?memodict?作為第一個(gè)參數(shù)。
這些魔法方法有什么用武之地呢?像往常一樣,當(dāng)你需要比默認(rèn)行為更加精確的控制時(shí)。例如,如果你想拷貝一個(gè)對(duì)象,其中存儲(chǔ)了一個(gè)字典作為緩存(可能會(huì)很大),拷貝緩存可能是沒有意義的。如果這個(gè)緩存可以在內(nèi)存中被不同實(shí)例共享,那么它就應(yīng)該被共享。
11.13. Pickling
如果你和其他的Python愛好者共事過,很可能你已經(jīng)聽說過Pickling了。Pickling是Python數(shù)據(jù)結(jié)構(gòu)的序列化過程,當(dāng)你想存儲(chǔ)一個(gè)對(duì)象稍后再取出讀取時(shí),Pickling會(huì)顯得十分有用。然而它同樣也是擔(dān)憂和混淆的主要來源。
Pickling是如此的重要,以至于它不僅僅有自己的模塊(?pickle?),還有自己的協(xié)議和魔法方法。首先,我們先來簡(jiǎn)要的介紹一下如何pickle已存在的對(duì)象類型(如果你已經(jīng)知道了,大可跳過這部分內(nèi)容)。
11.13.1. Pickling : 小試牛刀
我們一起來pickle吧。假設(shè)你有一個(gè)字典,你想存儲(chǔ)它,稍后再取出來。你可以把它的內(nèi)容寫入一個(gè)文件,小心翼翼地確保使用了正確地格式,要把它讀取出來,你可以使用?exec()?或處理文件輸入。但是這種方法并不可靠:如果你使用純文本來存儲(chǔ)重要數(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()
將會(huì)發(fā)生什么?正如你期待的,它就是我們之前的?data?。
現(xiàn)在,還需要謹(jǐn)慎地說一句: pickle并不完美。Pickle文件很容易因?yàn)槭鹿驶虮还室獾钠茐牡簟ickling或許比純文本文件安全一些,但是依然有可能被用來運(yùn)行惡意代碼。而且它還不支持跨Python版本,所以不要指望分發(fā)pickle對(duì)象之后所有人都能正確地讀取。然而不管怎么樣,它依然是一個(gè)強(qiáng)有力的工具,可以用于緩存和其他類型的持久化工作。
11.13.2. Pickle你的對(duì)象
Pickle不僅僅可以用于內(nèi)建類型,任何遵守pickle協(xié)議的類都可以被pickle。Pickle協(xié)議有四個(gè)可選方法,可以讓類自定義它們的行為(這和C語言擴(kuò)展略有不同,那不在我們的討論范圍之內(nèi))。
-
__getinitargs__(self)
如果你想讓你的類在反pickle時(shí)調(diào)用?__init__?,你可以定義__getinitargs__(self)?,它會(huì)返回一個(gè)參數(shù)元組,這個(gè)元組會(huì)傳遞給__init__?。注意,這個(gè)方法只能用于舊式類。
-
__getnewargs__(self)
對(duì)新式類來說,你可以通過這個(gè)方法改變類在反pickle時(shí)傳遞給?__new__?的參數(shù)。這個(gè)方法應(yīng)該返回一個(gè)參數(shù)元組。
-
__getstate__(self)
你可以自定義對(duì)象被pickle時(shí)被存儲(chǔ)的狀態(tài),而不使用對(duì)象的?__dict__?屬性。 這個(gè)狀態(tài)在對(duì)象被反pickle時(shí)會(huì)被?__setstate__?使用。
-
__setstate__(self)
當(dāng)一個(gè)對(duì)象被反pickle時(shí),如果定義了?__setstate__?,對(duì)象的狀態(tài)會(huì)傳遞給這個(gè)魔法方法,而不是直接應(yīng)用到對(duì)象的?__dict__?屬性。這個(gè)魔法方法和__getstate__?相互依存:當(dāng)這兩個(gè)方法都被定義時(shí),你可以在Pickle時(shí)使用任何方法保存對(duì)象的任何狀態(tài)。
-
__reduce__(self)
當(dāng)定義擴(kuò)展類型時(shí)(也就是使用Python的C語言API實(shí)現(xiàn)的類型),如果你想pickle它們,你必須告訴Python如何pickle它們。?__reduce__?被定義之后,當(dāng)對(duì)象被Pickle時(shí)就會(huì)被調(diào)用。它要么返回一個(gè)代表全局名稱的字符串,Pyhton會(huì)查找它并pickle,要么返回一個(gè)元組。這個(gè)元組包含2到5個(gè)元素,其中包括:一個(gè)可調(diào)用的對(duì)象,用于重建對(duì)象時(shí)調(diào)用;一個(gè)參數(shù)元素,供那個(gè)可調(diào)用對(duì)象使用;被傳遞給?__setstate__?的狀態(tài)(可選);一個(gè)產(chǎn)生被pickle的列表元素的迭代器(可選);一個(gè)產(chǎn)生被pickle的字典元素的迭代器(可選);
-
__reduce_ex__(self)
__reduce_ex__?的存在是為了兼容性。如果它被定義,在pickle時(shí)__reduce_ex__?會(huì)代替?__reduce__?被調(diào)用。?__reduce__?也可以被定義,用于不支持?__reduce_ex__?的舊版pickle的API調(diào)用。
11.13.3. 一個(gè)例子
我們的例子是?Slate?,它會(huì)記住它的值曾經(jīng)是什么,以及那些值是什么時(shí)候賦給它的。然而 每次被pickle時(shí)它都會(huì)變成空白,因?yàn)楫?dāng)前的值不會(huì)被存儲(chǔ):
import time class Slate: '''存儲(chǔ)一個(gè)字符串和一個(gè)變更日志的類 每次被pickle都會(huì)忘記它當(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 'Changelog for Slate object:' for k,v in self.history.items(): print '%s\t %s' % (k,v) def __getstate__(self): # 故意不返回self.value或self.last_change # 我們想在反pickle時(shí)得到一個(gè)空白的slate return self.history def __setstate__(self): # 使self.history = slate,last_change # 和value為未定義 self.history = state self.value, self.last_change = None, None
11.14. 總結(jié)
這本指南的目標(biāo)是使所有閱讀它的人都能有所收獲,無論他們有沒有使用Python或者進(jìn)行面向?qū)ο缶幊痰慕?jīng)驗(yàn)。如果你剛剛開始學(xué)習(xí)Python,你會(huì)得到寶貴的基礎(chǔ)知識(shí),了解如何寫出具有豐富特性的,優(yōu)雅而且易用的類。如果你是中級(jí)的Python程序員,你或許能掌握一些新的概念和技巧,以及一些可以減少代碼行數(shù)的好辦法。如果你是專家級(jí)別的Python愛好者,你又重新復(fù)習(xí)了一遍某些可能已經(jīng)忘掉的知識(shí),也可能順便了解了一些新技巧。無論你的水平怎樣,我希望這趟遨游Python特殊方法的旅行,真的對(duì)你產(chǎn)生了魔法般的效果(實(shí)在忍不住不說最后這個(gè)雙關(guān))。
11.15. 附錄1:如何調(diào)用魔法方法
一些魔法方法直接和內(nèi)建函數(shù)對(duì)應(yīng),這種情況下,如何調(diào)用它們是顯而易見的。然而,另外的情況下,調(diào)用魔法方法的途徑并不是那么明顯。這個(gè)附錄旨在展示那些不那么明顯的調(diào)用魔法方法的語法。
魔法方法 | 什么時(shí)候被調(diào)用 | 解釋 |
---|---|---|
__new__(cls [,...]) | instance = MyClass(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 | 一元加法符號(hào) |
__neg__(self) | -self | 一元減法符號(hào) |
__invert__(self) | ~self | 按位取反 |
__index__(self) | x[self] | 當(dāng)對(duì)象用于索引時(shí) |
__nonzero__(self) | bool(self) | 對(duì)象的布爾值 |
__getattr__(self, name) | self.name #name不存在 | 訪問不存在的屬性 |
__setattr__(self, name) | self.name = val | 給屬性賦值 |
__delattr_(self, name) | del 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è)對(duì)象 |
__iter__(self) | for x in self | 迭代 |
__contains__(self, value) | value in self, value not in self | 使用in進(jìn)行成員測(cè)試 |
__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 |
11.16. 附錄2:Python 3中的變化
在這里,我們記錄了幾個(gè)在對(duì)象模型方面 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__?。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
