Python 魔術(shù)方法指南?
介紹?此教程為我的數(shù)篇文章中的一個(gè)重點(diǎn)。主題是魔術(shù)方法。 什么是魔術(shù)方法?他們是面向?qū)ο蟮腜ython的一切。他們是可以給你的類增加”magic”的特殊方法。他們總是被雙下劃線所包圍(e.g. __init__ 或者 __lt__)。然而他們的文檔卻遠(yuǎn)沒有提供應(yīng)該有的內(nèi)容。Python中所有的魔術(shù)方法均在Python官方文檔中有相應(yīng)描述,但是對于他們的描述比較混亂而且組織比較松散。很難找到有一個(gè)例子(也許他們原本打算的很好,在開始語言參考中有描述很詳細(xì),然而隨之而來的確是枯燥的語法描述等等)。 所以,為了修補(bǔ)我認(rèn)為Python文檔應(yīng)該修補(bǔ)的瑕疵,我決定給Python中的魔術(shù)方法提供一些用平淡的語言和實(shí)例驅(qū)使的文檔。我在開始已經(jīng)寫了數(shù)篇博文,現(xiàn)在在這篇文章中對他們進(jìn)行總結(jié)。 我希望你能夠喜歡這篇文章。你可以將之當(dāng)做一個(gè)教程,一個(gè)補(bǔ)習(xí)資料,或者一個(gè)參考。本文章的目的僅僅是為Python中的魔術(shù)方法提供一個(gè)友好的教程。 構(gòu)造和初始化?每個(gè)人都知道一個(gè)最基本的魔術(shù)方法, __init__ 。通過此方法我們可以定義一個(gè)對象的初始操作。然而,當(dāng)我調(diào)用 x = SomeClass() 的時(shí)候, __init__ 并不是第一個(gè)被調(diào)用的方法。實(shí)際上,還有一個(gè)叫做 __new__ 的方法,來構(gòu)造這個(gè)實(shí)例。然后給在開始創(chuàng)建時(shí)候的初始化函數(shù)來傳遞參數(shù)。在對象生命周期的另一端,也有一個(gè) __del__ 方法。我們現(xiàn)在來近距離的看一看這三個(gè)方法: __new__(cls, [...) __new__ 是在一個(gè)對象實(shí)例化的時(shí)候所調(diào)用的第一個(gè)方法。它的第一個(gè)參數(shù)是這個(gè)類,其他的參數(shù)是用來直接傳遞給 __init__ 方法。 __new__ 方法相當(dāng)不常用,但是它有自己的特性,特別是當(dāng)繼承一個(gè)不可變的類型比如一個(gè)tuple或者string。我不希望在 __new__ 上有太多細(xì)節(jié),因?yàn)椴⒉皇呛苡杏锰帲窃?Python文檔 中有詳細(xì)的闡述。 __init__(self, […) 此方法為類的初始化方法。當(dāng)構(gòu)造函數(shù)被調(diào)用的時(shí)候的任何參數(shù)都將會(huì)傳給它。(比如如果我們調(diào)用 x = SomeClass(10, 'foo')),那么 __init__ 將會(huì)得到兩個(gè)參數(shù)10和foo。 __init__ 在Python的類定義中被廣泛用到。 __del__(self) 如果 __new__ 和 __init__ 是對象的構(gòu)造器的話,那么 __del__ 就是析構(gòu)器。它不實(shí)現(xiàn)語句 del x (所以代碼將不會(huì)翻譯為 x.__del__() )。它定義的是當(dāng)一個(gè)對象進(jìn)行垃圾回收時(shí)候的行為。當(dāng)一個(gè)對象在刪除的時(shí)候需要更多的清潔工作的時(shí)候此方法會(huì)很有用,比如套接字對象或者是文件對象。注意,因?yàn)楫?dāng)解釋器退出的時(shí)候如果對象還存在,不能保證 __del__ 能夠被執(zhí)行,所以 __del__ can’t serve as a replacement for good coding practices ()~~~~~~~ 放在一起的話,這里是一個(gè) __init__ 和 __del__ 實(shí)際使用的例子。 from os.path import join class FileObject: '''給文件對象進(jìn)行包裝從而確認(rèn)在刪除時(shí)文件流關(guān)閉''' def __init__(self, filepath='~', filename='sample.txt'): #讀寫模式打開一個(gè)文件 self.file = open(join(filepath, filename), 'r+') def __del__(self): self.file.close() del self.file 讓定制的類工作起來?使用Python的魔術(shù)方法的最大優(yōu)勢在于他們提供了一種簡單的方法來讓對象可以表現(xiàn)的像內(nèi)置類型一樣。那意味著你可以避免丑陋的,違反直覺的,不標(biāo)準(zhǔn)的的操作方法。在一些語言中,有一些操作很常用比如: if instance.equals(other_instance): # do something 在Python中你可以這樣。但是這會(huì)讓人迷惑且產(chǎn)生不必要的冗余。相同的操作因?yàn)椴煌膸鞎?huì)使用不同的名字,這樣會(huì)產(chǎn)生不必要的工作。然而有了魔術(shù)方法的力量,我們可以定義一個(gè)方法(本例中為 __eq__ ),就說明了我們的意思: if instance == other_instance: #do something 這只是魔術(shù)方法的功能的一小部分。它讓你可以定義符號的含義所以我們可以在我們的類中使用。就像內(nèi)置類型一樣。 用于比較的魔術(shù)方法?Python對實(shí)現(xiàn)對象的比較,使用魔術(shù)方法進(jìn)行了大的逆轉(zhuǎn),使他們非常直觀而不是笨拙的方法調(diào)用。而且還提供了一種方法可以重寫Python對對象比較的默認(rèn)行為(通過引用)。以下是這些方法和他們的作用。 __cmp__(self, other) __cmp__ 是最基本的用于比較的魔術(shù)方法。它實(shí)際上實(shí)現(xiàn)了所有的比較符號(<,==,!=,etc.),但是它的表現(xiàn)并不會(huì)總是如你所愿(比如,當(dāng)一個(gè)實(shí)例與另一個(gè)實(shí)例相等是通過一個(gè)規(guī)則來判斷,而一個(gè)實(shí)例大于另外一個(gè)實(shí)例是通過另外一個(gè)規(guī)則來判斷)。如果 self < other 的話 __cmp__ 應(yīng)該返回一個(gè)負(fù)數(shù),當(dāng) self == o 的時(shí)候會(huì)返回0 ,而當(dāng) self > other 的時(shí)候會(huì)返回正數(shù)。通常最好的一種方式是去分別定義每一個(gè)比較符號而不是一次性將他們都定義。但是 __cmp__ 方法是你想要實(shí)現(xiàn)所有的比較符號而一個(gè)保持清楚明白的一個(gè)好的方法。 __eq__(self, other) 定義了等號的行為, == 。 __ne__(self, other) 定義了不等號的行為, != 。 __lt__(self, other) 定義了小于號的行為, < 。 __gt__(self, other) 定義了大于等于號的行為, >= 。 舉一個(gè)例子,創(chuàng)建一個(gè)類來表現(xiàn)一個(gè)詞語。我們也許會(huì)想要比較單詞的字典序(通過字母表),通過默認(rèn)的字符串比較的方法就可以實(shí)現(xiàn),但是我們也想要通過一些其他的標(biāo)準(zhǔn)來實(shí)現(xiàn),比如單詞長度或者音節(jié)數(shù)量。在這個(gè)例子中,我們來比較長度實(shí)現(xiàn)。以下是實(shí)現(xiàn)代碼: class Word(str): '''存儲(chǔ)單詞的類,定義比較單詞的幾種方法''' def __new__(cls, word): # 注意我們必須要用到__new__方法,因?yàn)閟tr是不可變類型 # 所以我們必須在創(chuàng)建的時(shí)候?qū)⑺跏蓟? if ' ' in word: print "Value contains spaces. Truncating to first space." word = word[:word.index(' ')] #單詞是第一個(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è) Words 對象(通過使用 Word('foo') 和 Word('bar') 然后通過長度來比較它們。注意,我們沒有定義 __eq__ 和 __ne__ 方法。這是因?yàn)閷?huì)產(chǎn)生一些怪異的結(jié)果(比如 Word('foo') == Word('bar') 將會(huì)返回true)。這對于測試基于長度的比較不是很有意義。所以我們退回去,用 str 內(nèi)置來進(jìn)行比較。 現(xiàn)在你知道你不必定義每一個(gè)比較的魔術(shù)方法從而進(jìn)行豐富的比較。標(biāo)準(zhǔn)庫中很友好的在 functiontols 中提供給我們一個(gè)類的裝飾器定義了所有的豐富的比較函數(shù)。如果你只是定義 __eq__ 和另外一個(gè)(e.g. __gt__, __lt__,etc.)這個(gè)特性僅僅在Python 2.7中存在,但是你如果有機(jī)會(huì)碰到的話,那么將會(huì)節(jié)省大量的時(shí)間和經(jīng)理。你可以通過在你定義的類前放置 @total_ordering 來使用。 數(shù)值處理的魔術(shù)方法?如同你在通過比較符來比較類的實(shí)例的時(shí)候來創(chuàng)建很多方法,你也可以定義一些數(shù)值符號的特性。系緊你的安全帶,來吧,這里有很多內(nèi)容。為了組織方便,我將會(huì)把數(shù)值處理的方法來分成五類:一元操作符,普通算數(shù)操作符,反射算數(shù)操作符(之后會(huì)詳細(xì)說明),增量賦值,和類型轉(zhuǎn)換。 一元操作符和函數(shù)?僅僅有一個(gè)操作位的一元操作符和函數(shù)。比如絕對值,負(fù)等。 __pos__(self) 實(shí)現(xiàn)正號的特性(比如 +some_object) __neg__(self) 實(shí)現(xiàn)負(fù)號的特性(比如 -some_object) __abs__(self) 實(shí)現(xiàn)內(nèi)置 abs() 函數(shù)的特性。 __invert__(self) 實(shí)現(xiàn) ~ 符號的特性。為了說明這個(gè)特性。你可以查看 Wikipedia中的這篇文章 普通算數(shù)操作符?現(xiàn)在我們僅僅覆蓋了普通的二進(jìn)制操作符:+,-,*和類似符號。這些符號大部分來說都淺顯易懂。 __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í)現(xiàn)的整數(shù)除法。 __div__(self, other) 實(shí)現(xiàn) / 符號實(shí)現(xiàn)的除法。 __truediv__(self, other) 實(shí)現(xiàn)真除法。注意只有只用了 from __future__ import division 的時(shí)候才會(huì)起作用。 __mod__(self, other) 實(shí)現(xiàn)取模算法 % __divmod___(self, other) 實(shí)現(xiàn)內(nèi)置 divmod() 算法 __pow__ 實(shí)現(xiàn)使用 ** 的指數(shù)運(yùn)算 __lshift__(self, other) 實(shí)現(xiàn)使用 << 的按位左移動(dòng) __rshift__(self, other) 實(shí)現(xiàn)使用 >> 的按位左移動(dòng) __and__(self, other) 實(shí)現(xiàn)使用 & 的按位與 __or__(self, other) 實(shí)現(xiàn)使用 | 的按位或 __xor__(self, other) 實(shí)現(xiàn)使用 ^ 的按位異或 反運(yùn)算?下面我將會(huì)講解一些反運(yùn)算的知識。有些概念你可能會(huì)認(rèn)為恐慌或者是陌生。但是實(shí)際上非常簡單。以下是一個(gè)例子: some_object + other 這是一個(gè)普通的加法運(yùn)算,反運(yùn)算是相同的,只是把操作數(shù)調(diào)換了位置: other + some_object 所以,除了當(dāng)與其他對象操作的時(shí)候自己會(huì)成為第二個(gè)操作數(shù)之外,所有的這些魔術(shù)方法都與普通的操作是相同的。大多數(shù)情況下,反運(yùn)算的結(jié)果是與普通運(yùn)算相同的。所以你可以你可以將 __radd__ 與 __add__ 等價(jià)。 __radd__(self, other) 實(shí)現(xiàn)反加 __rsub__(self, other) 實(shí)現(xiàn)反減 __rmul__(self, other) 實(shí)現(xiàn)反乘 __rfloordiv__(self, other) 實(shí)現(xiàn) // 符號的反除 __rdiv__(self, other) 實(shí)現(xiàn) / 符號的反除 __rtruediv__(self, other) 實(shí)現(xiàn)反真除,只有當(dāng) from __future__ import division 的時(shí)候會(huì)起作用 __rmod__(self, other) 實(shí)現(xiàn) % 符號的反取模運(yùn)算 __rdivmod__(self, other) 當(dāng) divmod(other, self) 被調(diào)用時(shí),實(shí)現(xiàn)內(nèi)置 divmod() 的反運(yùn)算 __rpow__ 實(shí)現(xiàn) ** 符號的反運(yùn)算 __rlshift__(self, other) 實(shí)現(xiàn) << 符號的反左位移 __rrshift__(self, other) 實(shí)現(xiàn) >> 符號的反右位移 __rand__(self, other) 實(shí)現(xiàn) & 符號的反與運(yùn)算 __ror__(self, other) 實(shí)現(xiàn) | 符號的反或運(yùn)算 __xor__(self, other) 實(shí)現(xiàn) ^ 符號的反異或運(yùn)算 增量賦值?Python也有大量的魔術(shù)方法可以來定制增量賦值語句。你也許對增量賦值已經(jīng)很熟悉,它將操作符與賦值來結(jié)合起來。如果你仍然不清楚我在說什么的話,這里有一個(gè)例子: x = 5 x += 1 # in other words x = x + 1 __iadd__(self, other) 實(shí)現(xiàn)賦值加法 __isub__(self, other) 實(shí)現(xiàn)賦值減法 __imul__(self, other) 實(shí)現(xiàn)賦值乘法 __ifloordiv__(self, other) 實(shí)現(xiàn) //= 的賦值地板除 __idiv__(self, other) 實(shí)現(xiàn)符號 /= 的賦值除 __itruediv__(self, other) 實(shí)現(xiàn)賦值真除,只有使用 from __future__ import division 的時(shí)候才能使用 __imod_(self, other) 實(shí)現(xiàn)符號 %= 的賦值取模 __ipow__ 實(shí)現(xiàn)符號 **= 的賦值冪運(yùn)算 __ilshift__(self, other) 實(shí)現(xiàn)符號 <<= 的賦值位左移 __irshift__(self, other) 實(shí)現(xiàn)符號 >>= 的賦值位右移 __iand__(self, other) 實(shí)現(xiàn)符號 &= 的賦值位與 __ior__(self, other) 實(shí)現(xiàn)符號 |= 的賦值位或 __ixor__(self, other) 實(shí)現(xiàn)符號 |= 的賦值位異或 類型轉(zhuǎn)換魔術(shù)方法?Python也有很多的魔術(shù)方法來實(shí)現(xiàn)類似 float() 的內(nèi)置類型轉(zhuǎn)換特性。 __int__(self) 實(shí)現(xiàn)整形的強(qiáng)制轉(zhuǎn)換 __long__(self) 實(shí)現(xiàn)長整形的強(qiáng)制轉(zhuǎn)換 __float__(self) 實(shí)現(xiàn)浮點(diǎn)型的強(qiáng)制轉(zhuǎn)換 __complex__(self) 實(shí)現(xiàn)復(fù)數(shù)的強(qiáng)制轉(zhuǎn)換 __oct__(self) 實(shí)現(xiàn)八進(jìn)制的強(qiáng)制轉(zhuǎn)換 __hex__(self) 實(shí)現(xiàn)二進(jìn)制的強(qiáng)制轉(zhuǎn)換 __index__(self) 當(dāng)對象是被應(yīng)用在切片表達(dá)式中時(shí),實(shí)現(xiàn)整形強(qiáng)制轉(zhuǎn)換,如果你定義了一個(gè)可能在切片時(shí)用到的定制的數(shù)值型,你應(yīng)該定義 __index__ (詳見PEP357) __trunc__(self) 當(dāng)使用 math.trunc(self) 的時(shí)候被調(diào)用。 __trunc__ 應(yīng)該返回?cái)?shù)值被截取成整形(通常為長整形)的值 __coerce__(self, other) 實(shí)現(xiàn)混合模式算數(shù)。如果類型轉(zhuǎn)換不可能的話,那么 __coerce__ 將會(huì)返回 None ,否則他將對 self 和 other 返回一個(gè)長度為2的tuple,兩個(gè)為相同的類型。 表現(xiàn)你的類?如果有一個(gè)字符串來表示一個(gè)類將會(huì)非常有用。在Python中,有很多方法可以實(shí)現(xiàn)類定義內(nèi)置的一些函數(shù)的返回值。 __str__(self) 定義當(dāng) str() 調(diào)用的時(shí)候的返回值 __repr__(self) 定義 repr() 被調(diào)用的時(shí)候的返回值。 str() 和 repr() 的主要區(qū)別在于 repr() 返回的是機(jī)器可讀的輸出,而 str() 返回的是人類可讀的。 __unicode__(self) 定義當(dāng) unicode() 調(diào)用的時(shí)候的返回值。 unicode() 和 str() 很相似,但是返回的是unicode字符串。注意,如a果對你的類調(diào)用 str() 然而你只定義了 __unicode__() ,那么將不會(huì)工作。你應(yīng)該定義 __str__() 來確保調(diào)用時(shí)能返回正確的值。 __hash__(self) 定義當(dāng) hash() 調(diào)用的時(shí)候的返回值,它返回一個(gè)整形,用來在字典中進(jìn)行快速比較 __nonzero__(self) 定義當(dāng) bool() 調(diào)用的時(shí)候的返回值。本方法應(yīng)該返回True或者False,取決于你想讓它返回的值。 控制屬性訪問?許多從其他語言轉(zhuǎn)到Python的人會(huì)抱怨它缺乏類的真正封裝。(沒有辦法定義私有變量,然后定義公共的getter和setter)。Python其實(shí)可以通過魔術(shù)方法來完成封裝。我們來看一下: __getattr__(self, name) 你可以定義當(dāng)用戶試圖獲取一個(gè)不存在的屬性時(shí)的行為。這適用于對普通拼寫錯(cuò)誤的獲取和重定向,對獲取一些不建議的屬性時(shí)候給出警告(如果你愿意你也可以計(jì)算并且給出一個(gè)值)或者處理一個(gè) AttributeError 。只有當(dāng)調(diào)用不存在的屬性的時(shí)候會(huì)被返回。然而,這不是一個(gè)封裝的解決方案。 __setattr__(self, name, value) 與 __getattr__ 不同, __setattr__ 是一個(gè)封裝的解決方案。無論屬性是否存在,它都允許你定義對對屬性的賦值行為,以為這你可以對屬性的值進(jìn)行個(gè)性定制。但是你必須對使用 __setattr__ 特別小心。之后我們會(huì)詳細(xì)闡述。 __delattr__ 與 __setattr__ 相同,但是功能是刪除一個(gè)屬性而不是設(shè)置他們。注意與 __setattr__ 相同,防止無限遞歸現(xiàn)象發(fā)生。(在實(shí)現(xiàn) __delattr__ 的時(shí)候調(diào)用 del self.name 即會(huì)發(fā)生) __getattribute__(self, name) __getattribute__ 與它的同伴 __setattr__ 和 __delattr__ 配合非常好。但是我不建議使用它。只有在新類型類定義中才能使用 __getattribute__ (在最新版本Python中所有的類都是新類型,在老版本中你可以通過繼承 object 來制作一個(gè)新類。這樣你可以定義一個(gè)屬性值的訪問規(guī)則。有時(shí)也會(huì)產(chǎn)生一些帝歸現(xiàn)象。(這時(shí)候你可以調(diào)用基類的 __getattribute__ 方法來防止此現(xiàn)象的發(fā)生。)它可以消除對 __getattr__ 的使用,如果它被明確調(diào)用或者一個(gè) AttributeError 被拋出,那么當(dāng)實(shí)現(xiàn) __getattribute__ 之后才能被調(diào)用。此方法是否被使用其實(shí)最終取決于你的選擇。)我不建議使用它因?yàn)樗氖褂脦茁瘦^小(我們在取得一個(gè)值而不是設(shè)置一個(gè)值的時(shí)候有特殊的行為是非常罕見的。)而且它不能避免會(huì)出現(xiàn)bug。 在進(jìn)行屬性訪問控制定義的時(shí)候你可能會(huì)很容易的引起一個(gè)錯(cuò)誤??紤]下面的例子。 def __setattr__(self, name, value): self.name = value #每當(dāng)屬性被賦值的時(shí)候, ``__setattr__()`` 會(huì)被調(diào)用,這樣就造成了遞歸調(diào)用。 #這意味這會(huì)調(diào)用 ``self.__setattr__('name', value)`` ,每次方法會(huì)調(diào)用自己。這樣會(huì)造成程序崩潰。 def __setattr__(self, name, value): self.__dict__[name] = value #給類中的屬性名分配值 #定制特有屬性 Python的魔術(shù)方法非常強(qiáng)大,然而隨之而來的則是責(zé)任。了解正確的方法去使用非常重要。 所以我們對于定制屬性訪問權(quán)限了解了多少呢。它不應(yīng)該被輕易的使用。實(shí)際上,它非常強(qiáng)大。但是它存在的原因是:Python 不會(huì)試圖將一些不好的東西變得不可能,而是讓它們難以實(shí)現(xiàn)。自由是至高無上的,所以你可以做任何你想做的。一下是一個(gè)特別的屬性控制的例子(我們使用 super 因?yàn)椴皇撬械念惗加?__dict__ 屬性): class AccessCounter: '''一個(gè)包含計(jì)數(shù)器的控制權(quán)限的類每當(dāng)值被改變時(shí)計(jì)數(shù)器會(huì)加一''' 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) #如果你不想讓其他屬性被訪問的話,那么可以拋出 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)] 創(chuàng)建定制的序列?有很多方法讓你的Python類行為可以像內(nèi)置的序列(dict, tuple,list, string等等)。這是目前位置我最喜歡的魔術(shù)方法因?yàn)樗o你很搞的控制權(quán)限而且讓很多函數(shù)在你的類實(shí)例上工作的很出色。但是在開始之前,需要先講一些必須條件。 必須條件?現(xiàn)在我們開始講如何在Python中創(chuàng)建定制的序列,這個(gè)時(shí)候該講一講協(xié)議。協(xié)議(Protocols)與其他語言中的接口很相似。它給你很多你必須定義的方法。然而在Python中的協(xié)議是很不正式的,不需要明確聲明實(shí)現(xiàn)。事實(shí)上,他們更像一種指南。 我們?yōu)槭裁船F(xiàn)在討論協(xié)議?因?yàn)槿绻ㄖ迫萜黝愋偷脑捫枰玫竭@些協(xié)議。首先,實(shí)現(xiàn)不變?nèi)萜鞯脑捰幸粋€(gè)協(xié)議:實(shí)現(xiàn)不可變?nèi)萜鳎阒荒芏x __len__ 和 __getitem__ (一會(huì)會(huì)講更多)??勺?nèi)萜鲄f(xié)議則需要所有不可變?nèi)萜鞯乃辛硗膺€需要 __setitem__ 和 __delitem__ 。最終,如果你希望你的對象是可迭代的話,你需要定義 __iter__ 會(huì)返回一個(gè)迭代器。迭代器必須遵循迭代器協(xié)議,需要有 __iter__ (返回它本身) 和 next 。 容器后的魔法?這些是容器使用的魔術(shù)方法。 __len__(self) 然會(huì)容器長度。對于可變不可變?nèi)萜鞫夹枰械膮f(xié)議的一部分。 __getitem__(self, key) 定義當(dāng)一個(gè)條目被訪問時(shí),使用符號 self[key] 。這也是不可變?nèi)萜骱涂勺內(nèi)萜鞫家械膮f(xié)議的一部分。如果鍵的類型錯(cuò)誤和 KeyError 或者沒有合適的值。那么應(yīng)該拋出適當(dāng)?shù)?TypeError 異常。 __setitem__(self, key, value) 定義當(dāng)一個(gè)條目被賦值時(shí)的行為,使用 self[key] = value 。這也是可變?nèi)萜骱筒豢勺內(nèi)萜鲄f(xié)議中都要有的一部分。 __delitem__(self, key) 定義當(dāng)一個(gè)條目被刪除時(shí)的行為(比如 del self[key])。這只是可變?nèi)萜鲄f(xié)議中的一部分。當(dāng)使用一個(gè)無效的鍵時(shí)應(yīng)該拋出適當(dāng)?shù)漠惓!?__iter__(self) 返回一個(gè)容器的迭代器。很多情況下會(huì)返回迭代器,尤其是當(dāng)內(nèi)置的 iter() 方法被調(diào)用的時(shí)候,或者當(dāng)使用 for x in container 方式循環(huán)的時(shí)候。迭代器是他們本身的對象,他們必須定義返回 self 的 __iter__ 方法。 __reversed__(self) 實(shí)現(xiàn)當(dāng) reversed() 被調(diào)用時(shí)的行為。應(yīng)該返回列表的反轉(zhuǎn)版本。 __contains__(self, item) 當(dāng)調(diào)用 in 和 not in 來測試成員是否存在時(shí)候 __contains__ 被定義。你問為什么這個(gè)不是序列協(xié)議的一部分?那是因?yàn)楫?dāng) __contains__ 沒有被定義的時(shí)候,Python會(huì)迭代這個(gè)序列并且當(dāng)找到需要的值時(shí)會(huì)返回 True 。 __concat__(self, other) 最終,你可以通過 __concat__ 來定義當(dāng)用其他的來連接兩個(gè)序列時(shí)候的行為。當(dāng) + 操作符被調(diào)用時(shí)候會(huì)返回一個(gè) self 和 other.__concat__ 被調(diào)用后的結(jié)果產(chǎn)生的新序列。 一個(gè)例子?在我們的例子中,讓我們看一看你可能在其他語言中 用到的函數(shù)構(gòu)造語句的實(shí)現(xiàn)(比如 Haskell)。 class FunctionalList: '''一個(gè)封裝了一些附加魔術(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ì)拋出錯(cuò)誤 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): return self.values[0] def tail(self): return self.values[1:] def init(self): #返回一直到末尾的所有元素 return self.values[:-1] def last(self): #返回末尾元素 return self.values[-1] def drop(self, n): #返回除前n個(gè)外的所有元素 return self.values[n:] def take(self, n): #返回前n個(gè)元素 return self.values[:n] 反射?你可以通過魔術(shù)方法控制控制使用 isinstance() 和 issubclass() 內(nèi)置方法的反射行為。這些魔術(shù)方法是: __instancecheck__(self, instance) 檢查一個(gè)實(shí)例是不是你定義的類的實(shí)例 __subclasscheck__(self, subclass) 檢查一個(gè)類是不是你定義的類的子類 這些方法的用例似乎很少,這也許是真的。我不會(huì)花更多的時(shí)間在這些魔術(shù)方法上因?yàn)樗麄儾⒉皇呛苤匾?,但是他們的確反應(yīng)了Python 中的面向?qū)ο缶幊痰囊恍┗咎匦?非常容易的去做一些事情,即使并不是很必須。這些魔術(shù)方法看起來并不是很有用,但是當(dāng)你需要的時(shí)候你會(huì)很高興有這種特性。 可以調(diào)用的對象?你也許已經(jīng)知道,在Python中,方法也是一種高等的對象。這以為著他們也可以被傳遞到方法中就像其他對象一樣。這是一個(gè)非常驚人的特性。 在Python中,一個(gè)特殊的魔術(shù)方法可以讓類的實(shí)例的行為表現(xiàn)的像函數(shù)一樣,你可以調(diào)用他們,將一個(gè)函數(shù)當(dāng)做一個(gè)參數(shù)傳到另外一個(gè)函數(shù)中等等。這是一個(gè)非常強(qiáng)大的特性讓Python編程更加舒適甜美。 __call__(self, [args...]) 允許一個(gè)類的實(shí)例像函數(shù)一樣被調(diào)用。實(shí)質(zhì)上說,這意味著 x() 與 x.__call__() 是相同的。注意 __call__ 參數(shù)可變。這意味著你可以定義 __call__ 為其他你想要的函數(shù),無論有多少個(gè)參數(shù)。 __call__ 在那些類的實(shí)例經(jīng)常改變狀態(tài)的時(shí)候會(huì)非常有效。調(diào)用這個(gè)實(shí)例是一種改變這個(gè)對象狀態(tài)的直接和優(yōu)雅的做法。用一個(gè)實(shí)例來表達(dá)最好不過了: class Entity: '''調(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 會(huì)話管理?在Python 2.5中,為了代碼利用定義了一個(gè)新的關(guān)鍵詞 with 語句。會(huì)話控制在Python中不罕見(之前是作為庫的一部分被實(shí)現(xiàn)),直到 PEP343 被添加后。它被成為一級語言結(jié)構(gòu)。你也許之前看到這樣的語句: with open('foo.txt') as bar: # perform some action with bar 回話控制器通過包裝一個(gè) with 語句來設(shè)置和清理行為?;卦捒刂破鞯男袨橥ㄟ^兩個(gè)魔術(shù)方法來定義: __enter__(self) 定義當(dāng)使用 with 語句的時(shí)候會(huì)話管理器應(yīng)該初始塊被創(chuàng)建的時(shí)候的行為。注意 __enter__ 的返回值被 with 語句的目標(biāo)或者 as 后的名字綁定。 __exit__(self, exception_type, exception_value, traceback) 定義當(dāng)一個(gè)代碼塊被執(zhí)行或者終止后會(huì)話管理器應(yīng)該做什么。它可以被用來處理異常,清楚工作或者做一些代碼塊執(zhí)行完畢之后的日常工作。如果代碼塊執(zhí)行成功, exception_type , exception_value , 和 traceback 將會(huì)是 None 。否則的話你可以選擇處理這個(gè)異?;蛘呤侵苯咏唤o用戶處理。如果你想處理這個(gè)異常的話,確認(rèn) __exit__ 在所有結(jié)束之后會(huì)返回 True 。如果你想讓異常被會(huì)話管理器處理的話,那么就這樣處理。 __enter 和 __exit__ 對于明確有定義好的和日常行為的設(shè)置和清潔工作的類很有幫助。你也可以使用這些方法來創(chuàng)建一般的可以包裝其他對象的會(huì)話管理器。以下是一個(gè)例子。 class Closer: '''通過with語句和一個(gè)close方法來關(guān)閉一個(gè)對象的會(huì)話管理器''' def __init__(self, obj): self.obj = obj def __enter__(self): return self.obj # bound to target def __exit__(self, exception_type, exception_val, trace): try: self.obj.close() except AttributeError: # obj isn't closable print 'Not closable.' return True # exception handled successfully 以下是一個(gè)使用 Closer 的例子,使用一個(gè)FTP鏈接來證明(一個(gè)可關(guān)閉的套接字): >>> from magicmethods import Closer >>> from ftplib import FTP >>> with Closer(FTP('ftp.somesite.com')) as conn: ... conn.dir() ... >>> conn.dir() >>> with Closer(int(5)) as i: ... i += 1 ... Not closable. >>> i 6 你已經(jīng)看到了我們的包裝器如何靜默的處理適當(dāng)和不適當(dāng)?shù)氖褂眯袨?。這是會(huì)話管理器和魔術(shù)方法的強(qiáng)大功能。 創(chuàng)建對象的描述器?描述器是通過得到,設(shè)置,刪除的時(shí)候被訪問的類。當(dāng)然也可以修改其他的對象。描述器并不是鼓勵(lì)的,他們注定被一個(gè)所有者類所持有。當(dāng)創(chuàng)建面向?qū)ο蟮臄?shù)據(jù)庫或者類,里面含有相互依賴的屬性時(shí),描述器將會(huì)非常有用。一種典型的使用方法是用不同的單位表示同一個(gè)數(shù)值,或者表示某個(gè)數(shù)據(jù)的附加屬性(比如坐標(biāo)系上某個(gè)點(diǎn)包含了這個(gè)點(diǎn)到遠(yuǎn)點(diǎn)的距離信息)。 為了構(gòu)建一個(gè)描述器,一個(gè)類必須有至少 __get__ 或者 __set__ 其中一個(gè),并且 __delete__ 被實(shí)現(xiàn)。讓我們看看這些魔術(shù)方法。 __get__(self, instance, owner) 定義當(dāng)描述器的值被取得的時(shí)候的行為, instance 是擁有者對象的一個(gè)實(shí)例。 owner 是擁有者類本身。 __set__(self, instance, value) 定義當(dāng)描述器值被改變時(shí)候的行為。 instance 是擁有者類的一個(gè)實(shí)例 value 是要設(shè)置的值。 __delete__(self, instance) 定義當(dāng)描述器的值被刪除的行為。``instance`` 是擁有者對象的實(shí)例。 以下是一個(gè)描述器的實(shí)例:單位轉(zhuǎn)換。 class Meter(object): '''Descriptor for a meter.''' 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): '''Descriptor for a foot.''' def __get__(self, instance, owner): return instance.meter * 3.2808 def __set__(self, instance, value): instance.meter = float(value) / 3.2808 class Distance(object): '''Class to represent distance holding two descriptors for feet and meters.''' meter = Meter() foot = Foot() 儲(chǔ)存你的對象?如果你接觸過其他的 Pythoner,你可能已經(jīng)聽說過 Pickle 了, Pickle 是用來序列化 Python 數(shù)據(jù)結(jié)構(gòu)的模塊,在你需要暫時(shí)存儲(chǔ)一個(gè)對象的時(shí)候(比如緩存),這個(gè)模塊非常的有用,不過這同時(shí)也是隱患的誕生地。 序列化數(shù)據(jù)是一個(gè)非常重要的功能,所以他不僅僅擁有相關(guān)的模塊( Pickle , cPickle ),還有自己的協(xié)議以及魔術(shù)方法,不過首先,我們先討論下關(guān)于序列化內(nèi)建數(shù)據(jù)結(jié)構(gòu)的方法。 Pickling: 簡單例子?讓我們深入研究 Pickle,比如說你現(xiàn)在需要臨時(shí)儲(chǔ)存一個(gè)字典,你可以把它寫入到一個(gè)文件里,并且要小心翼翼的確保格式正確,之后再用 exec() 或者處理文件輸入來恢復(fù)數(shù)據(jù),實(shí)際上這是很不安全的,如果你使用文本存儲(chǔ)了一些重要的數(shù)據(jù),任何方式的改變都可能會(huì)影響到你的程序,輕則程序崩潰,重則被惡意程序利用,所以,讓我們用 Pickle 代替這種方式: import pickle data = {'foo': [1, 2, 3], 'bar': ('Hello', 'world!'), 'baz': True} jar = open('data.pkl', 'wb') pickle.dump(data, jar) # write the pickled data to the file jar jar.close() 嗯,過了幾個(gè)小時(shí)之后,我們需要用到它了,只需把它 unpickle 了就行了: import pickle pkl_file = open('data.pkl', 'rb') # connect to the pickled data data = pickle.load(pkl_file) # load it into a variable print data pkl_file.close() 正如你期望的,數(shù)據(jù)原封不動(dòng)的回來了! 同時(shí)要給你一句忠告: pickle 并不是很完美, Pickle 文件很容易被不小心或者故意損壞, Pickle 文件比純文本文件要稍微安全一點(diǎn),但是還是可以被利用運(yùn)行惡意程序。 Pickle 不是跨版本兼容的(譯注:最近剛好在 《Python Cookbook》上看到相關(guān)討論,書中描述的 Pickle 是跨版本兼容的,此點(diǎn)待驗(yàn)證),所以盡量不要去分發(fā) Pickle 過的文本,因?yàn)閯e人并不一定能夠打開。不過在做緩存或者其他需要序列化數(shù)據(jù)的時(shí)候, Pickle 還是很有用處的。 序列化你自己的對象?Pickle 并不是只支持內(nèi)建數(shù)據(jù)結(jié)果,任何遵循 Pickle 協(xié)議的類都可以,Pickle 協(xié)議為 Python 對象規(guī)定了4個(gè)可選方法來自定義 Pickle 行為(對于 C 擴(kuò)展的 cPickle 模塊會(huì)有一些不同,但是這并不在我們的討論范圍內(nèi)): __getinitargs__(self) 如果你希望在逆序列化的同時(shí)調(diào)用 __init__ ,你可以定義 __getinitargs__ 方法,這個(gè)方法應(yīng)該返回一系列你想被 __init__ 調(diào)用的參數(shù),注意這個(gè)方法只對老樣式的類起作用。 __getnewargs__(self) 對于新式的類,你可以定義任何在重建對象時(shí)候傳遞到 __new__ 方法中的參數(shù)。這個(gè)方法也應(yīng)該返回一系列的被 __new__ 調(diào)用的參數(shù)。 __getstate__(self) 你可以自定義當(dāng)對象被序列化時(shí)返回的狀態(tài),而不是使用 __dict 方法,當(dāng)逆序列化對象的時(shí)候,返回的狀態(tài)將會(huì)被 __setstate__ 方法調(diào)用。 __setstate__(self, state) 在對象逆序列化的時(shí)候,如果 __setstate__ 定義過的話,對象的狀態(tài)將被傳給它而不是傳給 __dict__ 。這個(gè)方法是和 __getstate__ 配對的,當(dāng)這兩個(gè)方法都被定義的時(shí)候,你就可以完全控制整個(gè)序列化與逆序列化的過程了。 例子?我們以 Slate 為例,這是一段記錄一個(gè)值以及這個(gè)值是何時(shí)被寫入的程序,但是,這個(gè) Slate 有一點(diǎn)特殊的地方,當(dāng)前值不會(huì)被保存。 import time class Slate: '''Class to store a string and a changelog, and forget its value when pickled.''' def __init__(self, value): self.value = value self.last_change = time.asctime() self.history = {} def change(self, new_value): # Change the value. Commit last value to history self.history[self.last_change] = self.value self.value = new_value self.last_change = time.asctime() def print_changes(self): print 'Changelog for Slate object:' for k, v in self.history.items(): print '%s\t %s' % (k, v) def __getstate__(self): # Deliberately do not return self.value or self.last_change. # We want to have a "blank slate" when we unpickle. return self.history def __setstate__(self, state): # Make self.history = state and last_change and value undefined self.history = state self.value, self.last_change = None, None 結(jié)論?這份指南的希望為所有人都能帶來一些知識,即使你是 Python 大?;蛘邔τ诰ㄓ诿嫦?qū)ο箝_發(fā)。如果你是一個(gè) Python 初學(xué)者,閱讀這篇文章之后你已經(jīng)獲得了編寫豐富,優(yōu)雅,靈活的類的知識基礎(chǔ)了。如果你是一個(gè)有一些經(jīng)驗(yàn)的 Python 程序員,你可能會(huì)發(fā)現(xiàn)一些能讓你寫的代碼更簡潔的方法。如果你是一個(gè) Python 大牛,可能會(huì)幫助你想起來一些你已經(jīng)遺忘的知識,或者一些你還沒聽說過的新功能。不管你現(xiàn)在有多少經(jīng)驗(yàn),我希望這次對于 Python 特殊方法的旅程能夠帶給你一些幫助(用雙關(guān)語真的很不錯(cuò) XD)(譯注: 這里的雙關(guān)在于標(biāo)題為 Magic Methods 這里是 神奇的旅程 ,不過由于中英語序的問題,直譯略顯頭重腳輕,所以稍微變化了下意思,丟掉了雙關(guān)的含義)。 附錄:如何調(diào)用魔術(shù)方法?一些魔術(shù)方法直接和內(nèi)建函數(shù)相對,在這種情況下,調(diào)用他們的方法很簡單,但是,如果是另外一種不是特別明顯的調(diào)用方法,這個(gè)附錄介紹了很多并不是很明顯的魔術(shù)方法的調(diào)用形式。
希望這個(gè)表格對你對于什么時(shí)候應(yīng)該使用什么方法這個(gè)問題有所幫助。 |
|