Python 魔术方法指南
http://pycoders-weekly-chinese.readthedocs.org/en/latest/issue6/a-guide-to-pythons-magic-methods.html
Python 魔術方法指南
-
入門
-
構造和初始化
- 構造定制類
- 用于比較的魔術方法
- 用于數值處理的魔術方法
-
表現你的類
-
控制屬性訪問
-
創建定制序列
-
反射
-
可以調用的對象
-
會話管理器
-
創建描述器對象
-
持久化對象
-
總結
-
附錄
介紹
此教程為我的數篇文章中的一個重點。主題是魔術方法。 什么是魔術方法?他們是面向對象的Python的一切。他們是可以給你的類增加”magic”的特殊方法。他們總是被雙下劃線所包圍(e.g.?__init__?或者?__lt__)。然而他們的文檔卻遠沒有提供應該有的內容。Python中所有的魔術方法均在Python官方文檔中有相應描述,但是對于他們的描述比較混亂而且組織比較松散。很難找到有一個例子(也許他們原本打算的很好,在開始語言參考中有描述很詳細,然而隨之而來的確是枯燥的語法描述等等)。
所以,為了修補我認為Python文檔應該修補的瑕疵,我決定給Python中的魔術方法提供一些用平淡的語言和實例驅使的文檔。我在開始已經寫了數篇博文,現在在這篇文章中對他們進行總結。
我希望你能夠喜歡這篇文章。你可以將之當做一個教程,一個補習資料,或者一個參考。本文章的目的僅僅是為Python中的魔術方法提供一個友好的教程。
構造和初始化
每個人都知道一個最基本的魔術方法,?__init__?。通過此方法我們可以定義一個對象的初始操作。然而,當我調用?x?=?SomeClass()?的時候,?__init__?并不是第一個被調用的方法。實際上,還有一個叫做?__new__?的方法,來構造這個實例。然后給在開始創建時候的初始化函數來傳遞參數。在對象生命周期的另一端,也有一個?__del__?方法。我們現在來近距離的看一看這三個方法:
__new__(cls,?[...)?__new__?是在一個對象實例化的時候所調用的第一個方法。它的第一個參數是這個類,其他的參數是用來直接傳遞給?__init__?方法。?__new__?方法相當不常用,但是它有自己的特性,特別是當繼承一個不可變的類型比如一個tuple或者string。我不希望在?__new__?上有太多細節,因為并不是很有用處,但是在?Python文檔?中有詳細的闡述。
__init__(self,?[…)?此方法為類的初始化方法。當構造函數被調用的時候的任何參數都將會傳給它。(比如如果我們調用?x?=?SomeClass(10,?'foo')),那么?__init__?將會得到兩個參數10和foo。?__init__?在Python的類定義中被廣泛用到。
__del__(self)?如果?__new__?和?__init__?是對象的構造器的話,那么?__del__?就是析構器。它不實現語句?del?x?(以上代碼將不會翻譯為?x.__del__()?)。它定義的是當一個對象進行垃圾回收時候的行為。當一個對象在刪除的時需要更多的清潔工作的時候此方法會很有用,比如套接字對象或者是文件對象。注意,如果解釋器退出的時候對象還存存在,就不能保證?__del__?能夠被執行,所以?__del__?can’t serve as a replacement for good coding practices ()~~~~~~~
放在一起的話,這里是一個?__init__?和?__del__?實際使用的例子。
from os.path import joinclass FileObject:'''給文件對象進行包裝從而確認在刪除時文件流關閉'''def __init__(self, filepath='~', filename='sample.txt'):#讀寫模式打開一個文件self.file = open(join(filepath, filename), 'r+')def __del__(self):self.file.close()del self.file讓定制的類工作起來
使用Python的魔術方法的最大優勢在于他們提供了一種簡單的方法來讓對象可以表現的像內置類型一樣。那意味著你可以避免丑陋的,違反直覺的,不標準的的操作方法。在一些語言中,有一些操作很常用比如:
if instance.equals(other_instance):# do something在Python中你可以這樣。但是這會讓人迷惑且產生不必要的冗余。相同的操作因為不同的庫會使用不同的名字,這樣會產生不必要的工作。然而有了魔術方法的力量,我們可以定義一個方法(本例中為?__eq__?),就說明了我們的意思:
if instance == other_instance:#do something這只是魔術方法的功能的一小部分。它讓你可以定義符號的含義所以我們可以在我們的類中使用。就像內置類型一樣。
用于比較的魔術方法
Python對實現對象的比較,使用魔術方法進行了大的逆轉,使他們非常直觀而不是笨拙的方法調用。而且還提供了一種方法可以重寫Python對對象比較的默認行為(通過引用)。以下是這些方法和他們的作用。
__cmp__(self,?other)?__cmp__?是最基本的用于比較的魔術方法。它實際上實現了所有的比較符號(<,==,!=,etc.),但是它的表現并不會總是如你所愿(比如,當一個實例與另一個實例相等是通過一個規則來判斷,而一個實例大于另外一個實例是通過另外一個規則來判斷)。如果?self?<?other?的話?__cmp__?應該返回一個負數,當?self?==?other?的時候會返回0 ,而當?self?>?other?的時候會返回正數。通常最好的一種方式是去分別定義每一個比較符號而不是一次性將他們都定義。但是?__cmp__?方法是你想要實現所有的比較符號而一個保持清楚明白的一個好的方法。
__eq__(self,?other)?定義了等號的行為,?==?。
__ne__(self,?other)?定義了不等號的行為,?!=?。
__lt__(self,?other)?定義了小于號的行為,?<?。
__gt__(self,?other)?定義了大于等于號的行為,?>=?。
舉一個例子,創建一個類來表現一個詞語。我們也許會想要比較單詞的字典序(通過字母表),通過默認的字符串比較的方法就可以實現,但是我們也想要通過一些其他的標準來實現,比如單詞長度或者音節數量。在這個例子中,我們來比較長度實現。以下是實現代碼:
class Word(str): '''存儲單詞的類,定義比較單詞的幾種方法'''def __new__(cls, word):# 注意我們必須要用到__new__方法,因為str是不可變類型# 所以我們必須在創建的時候將它初始化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)現在,我們創建兩個?Words?對象(通過使用?Word('foo')?和?Word('bar')?然后通過長度來比較它們。注意,我們沒有定義?__eq__?和?__ne__?方法。這是因為將會產生一些怪異的結果(比如?Word('foo')?==?Word('bar')?將會返回true)。這對于測試基于長度的比較不是很有意義。所以我們退回去,用?str?內置來進行比較。
現在你知道你不必定義每一個比較的魔術方法從而進行豐富的比較。標準庫中很友好的在?functiontols?中提供給我們一個類的裝飾器定義了所有的豐富的比較函數。如果你只是定義?__eq__?和另外一個(e.g.?__gt__,?__lt__,etc.)這個特性僅僅在Python 2.7中存在,但是你如果有機會碰到的話,那么將會節省大量的時間和工作量。你可以通過在你定義的類前放置?@total_ordering?來使用。
數值處理的魔術方法
如同你在通過比較符來比較類的實例的時候來創建很多方法,你也可以定義一些數值符號的特性。系緊你的安全帶,來吧,這里有很多內容。為了組織方便,我將會把數值處理的方法來分成五類:一元操作符,普通算數操作符,反射算數操作符(之后會詳細說明),增量賦值,和類型轉換。
一元操作符和函數
僅僅有一個操作位的一元操作符和函數。比如絕對值,負等。
__pos__(self)?實現正號的特性(比如?+some_object)
__neg__(self)?實現負號的特性(比如?-some_object)
__abs__(self)?實現內置?abs()?函數的特性。
__invert__(self)?實現?~?符號的特性。為了說明這個特性。你可以查看?Wikipedia中的這篇文章
普通算數操作符
現在我們僅僅覆蓋了普通的二進制操作符:+,-,*和類似符號。這些符號大部分來說都淺顯易懂。
__add__(self,?other)?實現加法。?__sub__(self,?other)?實現減法。?__mul__(self,?other)?實現乘法。?__floordiv__(self,?other)?實現?//?符號實現的整數除法。?__div__(self,?other)?實現?/?符號實現的除法。?__truediv__(self,other)?實現真除法。注意只有只用了?from?__future__?import?division?的時候才會起作用。?__mod__(self,?other)?實現取模算法?%?__divmod___(self,?other)?實現內置?divmod()?算法?__pow__?實現使用?**?的指數運算__lshift__(self,?other)?實現使用?<<?的按位左移動?__rshift__(self,?other)?實現使用?>>?的按位左移動?__and__(self,?other)?實現使用?&?的按位與?__or__(self,?other)?實現使用?|?的按位或?__xor__(self,?other)?實現使用?^?的按位異或
反運算
下面我將會講解一些反運算的知識。有些概念你可能會認為恐慌或者是陌生。但是實際上非常簡單。以下是一個例子:
some_object + other這是一個普通的加法運算,反運算是相同的,只是把操作數調換了位置:
other + some_object所以,除了當與其他對象操作的時候自己會成為第二個操作數之外,所有的這些魔術方法都與普通的操作是相同的。大多數情況下,反運算的結果是與普通運算相同的。所以你可以你可以將?__radd__?與?__add__?等價。
__radd__(self,?other)?實現反加?__rsub__(self,?other)?實現反減?__rmul__(self,?other)?實現反乘?__rfloordiv__(self,?other)?實現?//?符號的反除?__rdiv__(self,?other)?實現?/?符號的反除?__rtruediv__(self,?other)?實現反真除,只有當?from?__future__?import?division?的時候會起作用?__rmod__(self,?other)?實現?%?符號的反取模運算?__rdivmod__(self,?other)?當?divmod(other,?self)?被調用時,實現內置?divmod()?的反運算?__rpow__?實現?**?符號的反運算__rlshift__(self,?other)?實現?<<?符號的反左位移?__rrshift__(self,?other)?實現?>>?符號的反右位移?__rand__(self,?other)?實現?&?符號的反與運算?__ror__(self,?other)?實現?|?符號的反或運算?__xor__(self,?other)?實現?^?符號的反異或運算
增量賦值
Python也有大量的魔術方法可以來定制增量賦值語句。你也許對增量賦值已經很熟悉,它將操作符與賦值來結合起來。如果你仍然不清楚我在說什么的話,這里有一個例子:
x = 5 x += 1 # in other words x = x + 1__iadd__(self,?other)?實現賦值加法?__isub__(self,?other)?實現賦值減法?__imul__(self,?other)?實現賦值乘法?__ifloordiv__(self,?other)?實現?//=?的賦值地板除?__idiv__(self,?other)?實現符號?/=?的賦值除?__itruediv__(self,other)?實現賦值真除,只有使用?from?__future__?import?division?的時候才能使用?__imod_(self,?other)?實現符號?%=?的賦值取模?__ipow__?實現符號?**=?的賦值冪運算?__ilshift__(self,?other)?實現符號?<<=?的賦值位左移__irshift__(self,?other)?實現符號?>>=?的賦值位右移?__iand__(self,?other)?實現符號?&=?的賦值位與?__ior__(self,?other)?實現符號?|=?的賦值位或?__ixor__(self,?other)?實現符號?|=?的賦值位異或
類型轉換魔術方法
Python也有很多的魔術方法來實現類似?float()?的內置類型轉換特性。?__int__(self)?實現整形的強制轉換?__long__(self)?實現長整形的強制轉換?__float__(self)?實現浮點型的強制轉換?__complex__(self)?實現復數的強制轉換__oct__(self)?實現八進制的強制轉換?__hex__(self)?實現二進制的強制轉換?__index__(self)?當對象是被應用在切片表達式中時,實現整形強制轉換,如果你定義了一個可能在切片時用到的定制的數值型,你應該定義?__index__?(詳見PEP357)?__trunc__(self)?當使用?math.trunc(self)?的時候被調用。?__trunc__?應該返回數值被截取成整形(通常為長整形)的值?__coerce__(self,?other)?實現混合模式算數。如果類型轉換不可能的話,那么?__coerce__?將會返回None?,否則他將對?self?和?other?返回一個長度為2的tuple,兩個為相同的類型。
表現你的類
如果有一個字符串來表示一個類將會非常有用。在Python中,有很多方法可以實現類定義內置的一些函數的返回值。?__str__(self)?定義當?str()?調用的時候的返回值?__repr__(self)?定義?repr()?被調用的時候的返回值。?str()和?repr()?的主要區別在于?repr()?返回的是機器可讀的輸出,而?str()?返回的是人類可讀的。?__unicode__(self)?定義當?unicode()?調用的時候的返回值。?unicode()?和?str()?很相似,但是返回的是unicode字符串。注意,如a果對你的類調用?str()?然而你只定義了?__unicode__()?,那么將不會工作。你應該定義?__str__()?來確保調用時能返回正確的值。
__hash__(self)?定義當?hash()?調用的時候的返回值,它返回一個整形,用來在字典中進行快速比較?__nonzero__(self)?定義當?bool()?調用的時候的返回值。本方法應該返回True或者False,取決于你想讓它返回的值。
控制屬性訪問
許多從其他語言轉到Python的人會抱怨它缺乏類的真正封裝。(沒有辦法定義私有變量,然后定義公共的getter和setter)。Python其實可以通過魔術方法來完成封裝。我們來看一下:
__getattr__(self,?name)?你可以定義當用戶試圖獲取一個不存在的屬性時的行為。這適用于對普通拼寫錯誤的獲取和重定向,對獲取一些不建議的屬性時候給出警告(如果你愿意你也可以計算并且給出一個值)或者處理一個AttributeError?。只有當調用不存在的屬性的時候會被返回。然而,這不是一個封裝的解決方案。?__setattr__(self,?name,?value)?與?__getattr__?不同,?__setattr__?是一個封裝的解決方案。無論屬性是否存在,它都允許你定義對對屬性的賦值行為,以為這你可以對屬性的值進行個性定制。但是你必須對使用?__setattr__?特別小心。之后我們會詳細闡述。?__delattr__?與?__setattr__?相同,但是功能是刪除一個屬性而不是設置他們。注意與?__setattr__?相同,防止無限遞歸現象發生。(在實現?__delattr__?的時候調用?del?self.name?即會發生)?__getattribute__(self,?name)?__getattribute__?與它的同伴?__setattr__?和?__delattr__?配合非常好。但是我不建議使用它。只有在新類型類定義中才能使用?__getattribute__?(在最新版本Python中所有的類都是新類型,在老版本中你可以通過繼承?object?來制作一個新類。這樣你可以定義一個屬性值的訪問規則。有時也會產生一些帝歸現象。(這時候你可以調用基類的__getattribute__?方法來防止此現象的發生。)它可以消除對?__getattr__?的使用,如果它被明確調用或者一個?AttributeError?被拋出,那么當實現?__getattribute__?之后才能被調用。此方法是否被使用其實最終取決于你的選擇。)我不建議使用它因為它的使用幾率較小(我們在取得一個值而不是設置一個值的時候有特殊的行為是非常罕見的。)而且它不能避免會出現bug。
在進行屬性訪問控制定義的時候你可能會很容易的引起一個錯誤。考慮下面的例子。
def __setattr__(self, name, value):self.name = value#每當屬性被賦值的時候, ``__setattr__()`` 會被調用,這樣就造成了遞歸調用。#這意味這會調用 ``self.__setattr__('name', value)`` ,每次方法會調用自己。這樣會造成程序崩潰。def __setattr__(self, name, value):self.__dict__[name] = value #給類中的屬性名分配值#定制特有屬性Python的魔術方法非常強大,然而隨之而來的則是責任。了解正確的方法去使用非常重要。
所以我們對于定制屬性訪問權限了解了多少呢。它不應該被輕易的使用。實際上,它非常強大。但是它存在的原因是:Python 不會試圖將一些不好的東西變得不可能,而是讓它們難以實現。自由是至高無上的,所以你可以做任何你想做的。以下是一個特別的屬性控制的例子(我們使用?super?因為不是所有的類都有?__dict__?屬性):
class AccessCounter:'''一個包含計數器的控制權限的類每當值被改變時計數器會加一'''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類行為可以像內置的序列(dict, tuple,list, string等等)。這是目前為止我最喜歡的魔術方法,因為它給你很搞的控制權限而且讓很多函數在你的類實例上工作的很出色。但是在開始之前,需要先講一些必須條件。
必須條件
現在我們開始講如何在Python中創建定制的序列,這個時候該講一講協議。協議(Protocols)與其他語言中的接口很相似。它給你很多你必須定義的方法。然而在Python中的協議是很不正式的,不需要明確聲明實現。事實上,他們更像一種指南。
我們為什么現在討論協議?因為如果要定制容器類型的話需要用到這些協議。首先,實現不變容器的話有一個協議:實現不可變容器,你只能定義?__len__?和?__getitem__?(一會會講更多)。可變容器協議則需要所有不可變容器的所有另外還需要?__setitem__?和?__delitem__?。最終,如果你希望你的對象是可迭代的話,你需要定義?__iter__?會返回一個迭代器。迭代器必須遵循迭代器協議,需要有?__iter__?(返回它本身) 和?next?。
容器后的魔法
這些是容器使用的魔術方法。?__len__(self)?然會容器長度。對于可變不可變容器都需要有的協議的一部分。?__getitem__(self,?key)?定義當一個條目被訪問時,使用符號?self[key]?。這也是不可變容器和可變容器都要有的協議的一部分。如果鍵的類型錯誤和?KeyError?或者沒有合適的值。那么應該拋出適當的?TypeError?異常。?__setitem__(self,?key,?value)?定義當一個條目被賦值時的行為,使用?self[key]?=?value?。這也是可變容器和不可變容器協議中都要有的一部分。?__delitem__(self,?key)?定義當一個條目被刪除時的行為(比如?del?self[key])。這只是可變容器協議中的一部分。當使用一個無效的鍵時應該拋出適當的異常。?__iter__(self)?返回一個容器的迭代器。很多情況下會返回迭代器,尤其是當內置的?iter()?方法被調用的時候,或者當使用?for?x?in?container?方式循環的時候。迭代器是他們本身的對象,他們必須定義返回?self?的?__iter__?方法。?__reversed__(self)?實現當?reversed()?被調用時的行為。應該返回列表的反轉版本。?__contains__(self,?item)?當調用?in?和?not?in?來測試成員是否存在時候?__contains__?被定義。你問為什么這個不是序列協議的一部分?那是因為當?__contains__?沒有被定義的時候,Python會迭代這個序列并且當找到需要的值時會返回?True?。?__concat__(self,?other)?最終,你可以通過?__concat__?來定義當用其他的來連接兩個序列時候的行為。當?+?操作符被調用時候會返回一個?self?和?other.__concat__?被調用后的結果產生的新序列。
一個例子
在我們的例子中,讓我們看一看你可能在其他語言中 用到的函數構造語句的實現(比如 Haskell)。
class FunctionalList: '''一個封裝了一些附加魔術方法比如 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]反射
你可以通過魔術方法控制控制使用?isinstance()?和?issubclass()?內置方法的反射行為。這些魔術方法是:
__instancecheck__(self,?instance)
檢查一個實例是不是你定義的類的實例
__subclasscheck__(self,?subclass)
檢查一個類是不是你定義的類的子類
這些方法的用例似乎很少,這也許是真的。我不會花更多的時間在這些魔術方法上因為他們并不是很重要,但是他們的確反應了Python 中的面向對象編程的一些基本特性:非常容易的去做一些事情,即使并不是很必須。這些魔術方法看起來并不是很有用,但是當你需要的時候你會很高興有這種特性。
可以調用的對象
你也許已經知道,在Python中,方法也是一種高等的對象。這意味著他們也可以被傳遞到方法中就像其他對象一樣。這是一個非常驚人的特性。 在Python中,一個特殊的魔術方法可以讓類的實例的行為表現的像函數一樣,你可以調用他們,將一個函數當做一個參數傳到另外一個函數中等等。這是一個非常強大的特性讓Python編程更加舒適甜美。?__call__(self,?[args...])
允許一個類的實例像函數一樣被調用。實質上說,這意味著?x()?與?x.__call__()?是相同的。注意?__call__?參數可變。這意味著你可以定義?__call__?為其他你想要的函數,無論有多少個參數。
__call__?在那些類的實例經常改變狀態的時候會非常有效。調用這個實例是一種改變這個對象狀態的直接和優雅的做法。用一個實例來表達最好不過了:
class Entity: '''調用實體來改變實體的位置。'''def __init__(self, size, x, y):self.x, self.y = x, yself.size = sizedef __call__(self, x, y):'''改變實體的位置'''self.x, self.y = x, y會話管理
在Python 2.5中,為了代碼利用定義了一個新的關鍵詞?with?語句。會話控制在Python中不罕見(之前是作為庫的一部分被實現),直到?PEP343?被添加后。它被成為一級語言結構。你也許之前看到這樣的語句:
with open('foo.txt') as bar: # perform some action with bar回話控制器通過包裝一個?with?語句來設置和清理行為。回話控制器的行為通過兩個魔術方法來定義:?__enter__(self)?定義當使用?with?語句的時候會話管理器應該初始塊被創建的時候的行為。注意?__enter__?的返回值被?with?語句的目標或者?as?后的名字綁定。?__exit__(self,?exception_type,?exception_value,?traceback)?定義當一個代碼塊被執行或者終止后會話管理器應該做什么。它可以被用來處理異常,清除工作或者做一些代碼塊執行完畢之后的日常工作。如果代碼塊執行成功,?exception_type?,?exception_value?, 和?traceback?將會是?None?。否則的話你可以選擇處理這個異常或者是直接交給用戶處理。如果你想處理這個異常的話,確認?__exit__?在所有結束之后會返回?True。如果你想讓異常被會話管理器處理的話,那么就這樣處理。
__enter?和?__exit__?對于明確有定義好的和日常行為的設置和清潔工作的類很有幫助。你也可以使用這些方法來創建一般的可以包裝其他對象的會話管理器。以下是一個例子。
class Closer: '''通過with語句和一個close方法來關閉一個對象的會話管理器'''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 closableprint 'Not closable.'return True # exception handled successfully以下是一個使用?Closer?的例子,使用一個FTP鏈接來證明(一個可關閉的套接字):
>>> 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你已經看到了我們的包裝器如何靜默的處理適當和不適當的使用行為。這是會話管理器和魔術方法的強大功能。
創建對象的描述器
描述器是通過得到,設置,刪除的時候被訪問的類。當然也可以修改其他的對象。描述器并不是鼓勵的,他們注定被一個所有者類所持有。當創建面向對象的數據庫或者類,里面含有相互依賴的屬性時,描述器將會非常有用。一種典型的使用方法是用不同的單位表示同一個數值,或者表示某個數據的附加屬性(比如坐標系上某個點包含了這個點到遠點的距離信息)。
為了構建一個描述器,一個類必須有至少?__get__?或者?__set__?其中一個,并且?__delete__?被實現。讓我們看看這些魔術方法。?__get__(self,?instance,?owner)?定義當描述器的值被取得的時候的行為,?instance?是擁有者對象的一個實例。?owner?是擁有者類本身。?__set__(self,?instance,?value)?定義當描述器值被改變時候的行為。?instance?是擁有者類的一個實例?value?是要設置的值。?__delete__(self,?instance)?定義當描述器的值被刪除的行為。instance?是擁有者對象的實例。 以下是一個描述器的實例:單位轉換。
class Meter(object): '''Descriptor for a meter.'''def __init__(self, value=0.0):self.value = float(value)def __get__(self, instance, owner):return self.valuedef __set__(self, instance, value):self.value = float(value)class Foot(object):'''Descriptor for a foot.'''def __get__(self, instance, owner):return instance.meter * 3.2808def __set__(self, instance, value):instance.meter = float(value) / 3.2808class Distance(object):'''Class to represent distance holding two descriptors for feet andmeters.'''meter = Meter()foot = Foot()儲存你的對象
如果你接觸過其他的 Pythoner,你可能已經聽說過 Pickle 了, Pickle 是用來序列化 Python 數據結構的模塊,在你需要暫時存儲一個對象的時候(比如緩存),這個模塊非常的有用,不過這同時也是隱患的誕生地。
序列化數據是一個非常重要的功能,所以他不僅僅擁有相關的模塊(?Pickle?,?cPickle?),還有自己的協議以及魔術方法,不過首先,我們先討論下關于序列化內建數據結構的方法。
Pickling: 簡單例子
讓我們深入研究 Pickle,比如說你現在需要臨時儲存一個字典,你可以把它寫入到一個文件里,并且要小心翼翼的確保格式正確,之后再用 exec() 或者處理文件輸入來恢復數據,實際上這是很不安全的,如果你使用文本存儲了一些重要的數據,任何方式的改變都可能會影響到你的程序,輕則程序崩潰,重則被惡意程序利用,所以,讓我們用 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 jar jar.close()嗯,過了幾個小時之后,我們需要用到它了,只需把它 unpickle 了就行了:
import picklepkl_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()正如你期望的,數據原封不動的回來了!
同時要給你一句忠告: pickle 并不是很完美, Pickle 文件很容易被不小心或者故意損壞, Pickle 文件比純文本文件要稍微安全一點,但是還是可以被利用運行惡意程序。 Pickle 不是跨版本兼容的(譯注:最近剛好在 《Python Cookbook》上看到相關討論,書中描述的 Pickle 是跨版本兼容的,此點待驗證),所以盡量不要去分發 Pickle 過的文本,因為別人并不一定能夠打開。不過在做緩存或者其他需要序列化數據的時候, Pickle 還是很有用處的。
序列化你自己的對象
Pickle 并不是只支持內建數據結果,任何遵循 Pickle 協議的類都可以,Pickle 協議為 Python 對象規定了4個可選方法來自定義 Pickle 行為(對于 C 擴展的 cPickle 模塊會有一些不同,但是這并不在我們的討論范圍內):
__getinitargs__(self)
如果你希望在逆序列化的同時調用?__init__?,你可以定義?__getinitargs__?方法,這個方法應該返回一系列你想被?__init__?調用的參數,注意這個方法只對老樣式的類起作用。
__getnewargs__(self)
對于新式的類,你可以定義任何在重建對象時候傳遞到?__new__?方法中的參數。這個方法也應該返回一系列的被?__new__?調用的參數。
__getstate__(self)
你可以自定義當對象被序列化時返回的狀態,而不是使用?__dict?方法,當逆序列化對象的時候,返回的狀態將會被?__setstate__?方法調用。
__setstate__(self,?state)
在對象逆序列化的時候,如果?__setstate__?定義過的話,對象的狀態將被傳給它而不是傳給?__dict__?。這個方法是和?__getstate__?配對的,當這兩個方法都被定義的時候,你就可以完全控制整個序列化與逆序列化的過程了。
例子
我們以 Slate 為例,這是一段記錄一個值以及這個值是何時被寫入的程序,但是,這個 Slate 有一點特殊的地方,當前值不會被保存。
import timeclass Slate:'''Class to store a string and a changelog, and forget its value when pickled.'''def __init__(self, value):self.value = valueself.last_change = time.asctime()self.history = {}def change(self, new_value):# Change the value. Commit last value to historyself.history[self.last_change] = self.valueself.value = new_valueself.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.historydef __setstate__(self, state):# Make self.history = state and last_change and value undefinedself.history = stateself.value, self.last_change = None, None結論
這份指南的希望為所有人都能帶來一些知識,即使你是 Python 大牛或者對于精通于面向對象開發。如果你是一個 Python 初學者,閱讀這篇文章之后你已經獲得了編寫豐富,優雅,靈活的類的知識基礎了。如果你是一個有一些經驗的 Python 程序員,你可能會發現一些能讓你寫的代碼更簡潔的方法。如果你是一個 Python 大牛,可能會幫助你想起來一些你已經遺忘的知識,或者一些你還沒聽說過的新功能。不管你現在有多少經驗,我希望這次對于 Python 特殊方法的旅程能夠帶給你一些幫助(用雙關語真的很不錯 XD)(譯注: 這里的雙關在于標題為 Magic Methods 這里是 神奇的旅程 ,不過由于中英語序的問題,直譯略顯頭重腳輕,所以稍微變化了下意思,丟掉了雙關的含義)。
附錄:如何調用魔術方法
一些魔術方法直接和內建函數相對,在這種情況下,調用他們的方法很簡單,但是,如果是另外一種不是特別明顯的調用方法,這個附錄介紹了很多并不是很明顯的魔術方法的調用形式。
| __new__(cls [,...]) | instance = MyClass(arg1, arg2) | __new__ 在創建實例的時候被調用 |
| __init__(self [,...]) | instance = MyClass(arg1, arg2) | __init__ 在創建實例的時候被調用 |
| __cmp__(self, other) | self == other, self > other, 等。 | 在比較的時候調用 |
| __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 操作測試關系時 |
| __concat__(self, value) | self + other | 連接兩個對象時 |
| __call__(self [,...]) | self(args) | “調用”對象時 |
| __enter__(self) | with self as x: | with 語句環境管理 |
| __exit__(self, exc, val, trace) | with self as x: | with 語句環境管理 |
| __getstate__(self) | pickle.dump(pkl_file, self) | 序列化 |
| __setstate__(self) | data = pickle.load(pkl_file) | 序列化 |
希望這個表格對你對于什么時候應該使用什么方法這個問題有所幫助。
總結
以上是生活随笔為你收集整理的Python 魔术方法指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 观察者模式实际应用场景「扩展点实战系列」
- 下一篇: Python(十三)IO编程