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