您好,登錄后才能下訂單哦!
原作者: Rafe Kettler
翻譯: hit9
原版(英文版) Repo:https://github.com/RafeKettler/magicmethods
本指南歸納于我的幾個月的博客,主題是 魔法方法 。
什么是魔法方法呢?它們在面向對象的Python的處處皆是。它們是一些可以讓你對類添加“魔法”的特殊方法。
它們經常是兩個下劃線包圍來命名的(比如 __init__
, __lt__
)。但是現在沒有很好的文檔來解釋它們。
所有的魔法方法都會在Python的官方文檔中找到,但是它們組織松散。而且很少會有示例(有的是無聊的語法描述,
語言參考)。
所以,為了修復我感知的Python文檔的缺陷,我開始提供更為通俗的,有示例支持的Python魔法方法指南。我一開始
寫了一些博文,現在我把這些博文總起來成為一篇指南。
希望你喜歡這篇指南,一篇友好,通俗易懂的Python魔法方法指南!
我們最為熟知的基本的魔法方法就是 __init__
,我們可以用它來指明一個對象初始化的行為。然而,當我們調用x = SomeClass()
的時候, __init__
并不是第一個被調用的方法。事實上,第一個被調用的是 __new__
,這個
方法才真正地創建了實例。當這個對象的生命周期結束的時候, __del__
會被調用。讓我們近一步理解這三個方法:
__new__(cls,[...)
__new__
是對象實例化時第一個調用的方法,它只取下 cls
參數,并把其他參數傳給 __init__
。 __new__
很少使用,但是也有它適合的場景,尤其是當類繼承自一個像元組或者字符串這樣不經常改變的類型的時候。我不打算深入討論__new__
,因為它并不是很有用, Python文檔 <http://www.python.org/download/releases/2.2/descrintro/#__new__>
_ 中
有詳細的說明。
__init__(self,[...])
類的初始化方法。它獲取任何傳給構造器的參數(比如我們調用 x = SomeClass(10, 'foo')
, __init__
就會接到參數10
和 'foo'
。 __init__
在Python的類定義中用的最多。
__del__(self)
__new__
和 __init__
是對象的構造器, __del__
是對象的銷毀器。它并非實現了語句 del x
(因此該語句不等同于 x.__del__()
)。而是定義了當對象被垃圾回收時的行為。
當對象需要在銷毀時做一些處理的時候這個方法很有用,比如 socket
對象、文件對象。但是需要注意的是,當Python解釋器退出但對象仍然存活的時候, __del__
并不會
執行。 所以養成一個手工清理的好習慣是很重要的,比如及時關閉連接。
這里有個 __init__
和 __del__
的例子::
from os.path import join
class FileObject:
'''文件對象的裝飾類,用來保證文件被刪除時能夠正確關閉。'''
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
使用Python魔法方法的一個巨大優勢就是可以構建一個擁有Python內置類型行為的對象。這意味著你可以避免使用非標準的、丑陋的方式來表達簡單的操作。
在一些語言中,這樣做很常見::
if instance.equals(other_instance):
# do something
你當然可以在Python也這么做,但是這樣做讓代碼變得冗長而混亂。不同的類庫可能對同一種比較操作采用不同的方法名稱,這讓使用者需要做很多沒有必要的工作。運用魔法方法的魔力,我們可以定義方法 __eq__
::
if instance == other_instance:
# do something
這是魔法力量的一部分,這樣我們就可以創建一個像內建類型那樣的對象了!
Python包含了一系列的魔法方法,用于實現對象之間直接比較,而不需要采用方法調用。同樣也可以重載Python默認的比較方法,改變它們的行為。下面是這些方法的列表:
__cmp__(self, other)
__cmp__
是所有比較魔法方法中最基礎的一個,它實際上定義了所有比較操作符的行為(<,==,!=,等等),但是它可能不能按照你需要的方式工作(例如,判斷一個實例和另一個實例是否相等采用一套標準,而與判斷一個實例是否大于另一實例采用另一套)。 __cmp__
應該在 self < other
時返回一個負整數,在 self == other
時返回0,在 self > other
時返回正整數。最好只定義你所需要的比較形式,而不是一次定義全部。 如果你需要實現所有的比較形式,而且它們的判斷標準類似,那么 __cmp__
是一個很好的方法,可以減少代碼重復,讓代碼更簡潔。
__eq__
(self, other)`
定義等于操作符(==)的行為。
__ne__(self, other)
定義不等于操作符(!=)的行為。
__lt__(self, other)
定義小于操作符(<)的行為。
__gt__(self, other)
定義大于操作符(>)的行為。
__le__(self, other)
定義小于等于操作符(<)的行為。
__ge__(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(' ')]
# Word現在包含第一個空格前的所有字母
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)
現在我們可以創建兩個 Word
對象( Word('foo')
和 Word('bar')
)然后根據長度來比較它們。注意我們沒有定義 __eq__
和 __ne__
,這是因為有時候它們會導致奇怪的結果(很明顯, Word('foo') == Word('bar')
得到的結果會是true)。根據長度測試是否相等毫無意義,所以我們使用 str
的實現來比較相等。
從上面可以看到,不需要實現所有的比較魔法方法,就可以使用豐富的比較操作。標準庫還在 functools
模塊中提供了一個類裝飾器,只要我們定義 __eq__
和另外一個操作符( __gt__
, __lt__
等),它就可以幫我們實現比較方法。這個特性只在 Python 2.7 中可用。當它可用時,它能幫助我們節省大量的時間和精力。要使用它,只需要它 @total_ordering
放在類的定義之上就可以了
就像你可以使用比較操作符來比較類的實例,你也可以定義數值操作符的行為。固定好你的安全帶,這樣的操作符真的有很多。看在組織的份上,我把它們分成了五類:一元操作符,常見算數操作符,反射算數操作符(后面會涉及更多),增強賦值操作符,和類型轉換操作符。
一元操作符只有一個操作符。
__pos__(self)
實現取正操作,例如 +some_object
。
__neg__(self)
實現取負操作,例如 -some_object
。
__abs__(self)
實現內建絕對值函數 abs()
操作。
__invert__(self)
實現取反操作符 ~
。
__round__(self, n)
實現內建函數 round()
,n 是近似小數點的位數。
__floor__(self)
實現 math.floor()
函數,即向下取整。
__ceil__(self)
實現 math.ceil()
函數,即向上取整。
__trunc__(self)
實現 math.trunc()
函數,即距離零最近的整數。
現在,我們來看看常見的二元操作符(和一些函數),像+,-,*之類的,它們很容易從字面意思理解。
__add__(self, other)
實現加法操作。
__sub__(self, other)
實現減法操作。
__mul__(self, other)
實現乘法操作。
__floordiv__(self, other)
實現使用 //
操作符的整數除法。
__div__(self, other)
實現使用 /
操作符的除法。
__truediv__(self, other)
實現 _true_
除法,這個函數只有使用 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__
。注意一點,操作符左側的對象(也就是上面的 other
)一定不要定義(或者產生 NotImplemented
異常) 操作符的非反射版本。例如,在上面的例子中,只有當 other
沒有定義 __add__
時 some_object.__radd__
才會被調用。
__radd__(self, other)
實現反射加法操作。
__rsub__(self, other)
實現反射減法操作。
__rmul__(self, other)
實現反射乘法操作。
__rfloordiv__(self, other)
實現使用 //
操作符的整數反射除法。
__rdiv__(self, other)
實現使用 /
操作符的反射除法。
__rtruediv__(self, other)
實現 _true_
反射除法,這個函數只有使用 from __future__ import division
時才有作用。
__rmod__(self, other)
實現 %
反射取余操作符。
__rdivmod__(self, other)
實現調用 divmod(other, self)
時 divmod
內建函數的操作。
__rpow__
實現 **
反射操作符。
__rlshift__(self, other)
實現反射左移位運算符 <<
的作用。
__rshift__(self, other)
實現反射右移位運算符 >>
的作用。
__rand__(self, other)
實現反射按位與運算符 &
。
__ror__(self, other)
實現反射按位或運算符 |
。
__rxor__(self, other)
實現反射按位異或運算符 ^
。
Python同樣提供了大量的魔法方法,可以用來自定義增強賦值操作的行為。或許你已經了解增強賦值,它融合了“常見”的操作符和賦值操作,如果你還是沒聽明白,看下面的例子::
x = 5
x += 1 # 也就是 x = x + 1
這些方法都應該返回左側操作數應該被賦予的值(例如, a += b
__iadd__
也許會返回 a + b
,這個結果會被賦給 a ),下面是方法列表:
__iadd__(self, other)
實現加法賦值操作。
__isub__(self, other)
實現減法賦值操作。
__imul__(self, other)
實現乘法賦值操作。
__ifloordiv__(self, other)
實現使用 //=
操作符的整數除法賦值操作。
__idiv__(self, other)
實現使用 /=
操作符的除法賦值操作。
__itruediv__(self, other)
實現 _true_
除法賦值操作,這個函數只有使用 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)
實現到int的類型轉換。
__long__(self)
實現到long的類型轉換。
__float__(self)
實現到float的類型轉換。
__complex__(self)
實現到complex的類型轉換。
__oct__(self)
實現到八進制數的類型轉換。
__hex__(self)
實現到十六進制數的類型轉換。
__index__(self)
實現當對象用于切片表達式時到一個整數的類型轉換。如果你定義了一個可能會用于切片操作的數值類型,你應該定義 __index__
。
__trunc__(self)
當調用 math.trunc(self)
時調用該方法, __trunc__
應該返回 self
截取到一個整數類型(通常是long類型)的值。
__coerce__(self)
該方法用于實現混合模式算數運算,如果不能進行類型轉換, __coerce__
應該返回 None
。反之,它應該返回一個二元組 self
和 other
,這兩者均已被轉換成相同的類型。
使用字符串來表示類是一個相當有用的特性。在Python中有一些內建方法可以返回類的表示,相對應的,也有一系列魔法方法可以用來自定義在使用這些內建函數時類的行為。
__str__(self)
定義對類的實例調用 str()
時的行為。
__repr__(self)
定義對類的實例調用 repr()
時的行為。 str()
和 repr()
最主要的差別在于“目標用戶”。 repr()
的作用是產生機器可讀的輸出(大部分情況下,其輸出可以作為有效的Python代碼),而 str()
則產生人類可讀的輸出。
__unicode__(self)
定義對類的實例調用 unicode()
時的行為。 unicode()
和 str()
很像,只是它返回unicode字符串。注意,如果調用者試圖調用 str()
而你的類只實現了 __unicode__()
,那么類將不能正常工作。所有你應該總是定義 __str__()
,以防有些人沒有閑情雅致來使用unicode。
__format__(self)
定義當類的實例用于新式字符串格式化時的行為,例如, "Hello, 0:abc!".format(a)
會導致調用 a.__format__("abc")
。當定義你自己的數值類型或字符串類型時,你可能想提供某些特殊的格式化選項,這種情況下這個魔法方法會非常有用。
__hash__(self)
定義對類的實例調用 hash()
時的行為。它必須返回一個整數,其結果會被用于字典中鍵的快速比較。同時注意一點,實現這個魔法方法通常也需要實現 __eq__
,并且遵守如下的規則: a == b
意味著 hash(a) == hash(b)
。
__nonzero__(self)
定義對類的實例調用 bool()
時的行為,根據你自己對類的設計,針對不同的實例,這個魔法方法應該相應地返回True或False。
__dir__(self)
定義對類的實例調用 dir()
時的行為,這個方法應該向調用者返回一個屬性列表。一般來說,沒必要自己實現 __dir__
。但是如果你重定義了 __getattr__
或者 __getattribute__
(下個部分會介紹),乃至使用動態生成的屬性,以實現類的交互式使用,那么這個魔法方法是必不可少的。
到這里,我們基本上已經結束了魔法方法指南中無聊并且例子匱乏的部分。既然我們已經介紹了較為基礎的魔法方法,是時候涉及更高級的內容了。
很多從其他語言轉向Python的人都抱怨Python的類缺少真正意義上的封裝(即沒辦法定義私有屬性然后使用公有的getter和setter)。然而事實并非如此。實際上Python不是通過顯式定義的字段和方法修改器,而是通過魔法方法實現了一系列的封裝。
__getattr__(self, name)
當用戶試圖訪問一個根本不存在(或者暫時不存在)的屬性時,你可以通過這個魔法方法來定義類的行為。這個可以用于捕捉錯誤的拼寫并且給出指引,使用廢棄屬性時給出警告(如果你愿意,仍然可以計算并且返回該屬性),以及靈活地處理AttributeError。只有當試圖訪問不存在的屬性時它才會被調用,所以這不能算是一個真正的封裝的辦法。
__setattr__(self, name, value)
和 __getattr__
不同, __setattr__
可以用于真正意義上的封裝。它允許你自定義某個屬性的賦值行為,不管這個屬性存在與否,也就是說你可以對任意屬性的任何變化都定義自己的規則。然后,一定要小心使用 __setattr__
,這個列表最后的例子中會有所展示。
__delattr__(self, name)
這個魔法方法和 __setattr__
幾乎相同,只不過它是用于處理刪除屬性時的行為。和 _setattr__
一樣,使用它時也需要多加小心,防止產生無限遞歸(在 __delattr__
的實現中調用 del self.name
會導致無限遞歸)。
__getattribute__(self, name)
__getattribute__
看起來和上面那些方法很合得來,但是最好不要使用它。 __getattribute__
只能用于新式類。在最新版的Python中所有的類都是新式類,在老版Python中你可以通過繼承 object
來創建新式類。 __getattribute__
允許你自定義屬性被訪問時的行為,它也同樣可能遇到無限遞歸問題(通過調用基類的 __getattribute__
來避免)。 __getattribute__
基本上可以替代 __getattr__
。只有當它被實現,并且顯式地被調用,或者產生 AttributeError
時它才被使用。 這個魔法方法可以被使用(畢竟,選擇權在你自己),我不推薦你使用它,因為它的使用范圍相對有限(通常我們想要在賦值時進行特殊操作,而不是取值時),而且實現這個方法很容易出現Bug。
自定義這些控制屬性訪問的魔法方法很容易導致問題,考慮下面這個例子::
def __setattr__(self, name. value):
self.name = value
# 因為每次屬性幅值都要調用 __setattr__(),所以這里的實現會導致遞歸
# 這里的調用實際上是 self.__setattr('name', value)。因為這個方法一直
# 在調用自己,因此遞歸將持續進行,直到程序崩潰
def __setattr__(self, name, value):
self.__dict__[name] = value # 使用 __dict__ 進行賦值
# 定義自定義行為
再次重申,Python的魔法方法十分強大,能力越強責任越大,了解如何正確的使用魔法方法更加重要。
到這里,我們對Python中自定義屬性存取控制有了什么樣的印象?它并不適合輕度的使用。實際上,它有些過分強大,而且違反直覺。然而它之所以存在,是因為一個更大的原則:Python不指望讓杜絕壞事發生,而是想辦法讓做壞事變得困難。自由是至高無上的權利,你真的可以隨心所欲。下面的例子展示了實際應用中某些特殊的屬性訪問方法(注意我們之所以使用 super
是因為不是所有的類都有 __dict__
屬性)::
class AccessCounter(object):
''' 一個包含了一個值并且實現了訪問計數器的類
每次值的變化都會導致計數器自增'''
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類表現得像是內建序列類型(字典,元組,列表,字符串等)。這些魔法方式是目前為止我最喜歡的。它們給了你難以置信的控制能力,可以讓你的類與一系列的全局函數完美結合。在了解激動人心的內容之前,首先你需要掌握一些預備知識。
既然講到創建自己的序列類型,就不得不說一說協議了。協議類似某些語言中的接口,里面包含的是一些必須實現的方法。在Python中,協議完全是非正式的,也不需要顯式的聲明,事實上,它們更像是一種參考標準。
為什么我們要講協議?因為在Python中實現自定義容器類型需要用到一些協議。首先,不可變容器類型有如下協議:想實現一個不可變容器,你需要定義 __len__
和 __getitem__
(后面會具體說明)。可變容器的協議除了上面提到的兩個方法之外,還需要定義 __setitem__
和 __delitem__
。最后,如果你想讓你的對象可以迭代,你需要定義 __iter__
,這個方法返回一個迭代器。迭代器必須遵守迭代器協議,需要定義 __iter__
(返回它自己)和 next
方法。
__len__(self)
返回容器的長度,可變和不可變類型都需要實現。
__getitem__(self, key)
定義對容器中某一項使用 self[key]
的方式進行讀取操作時的行為。這也是可變和不可變容器類型都需要實現的一個方法。它應該在鍵的類型錯誤式產生 TypeError
異常,同時在沒有與鍵值相匹配的內容時產生 KeyError
異常。
__setitem__(self, key)
定義對容器中某一項使用 self[key]
的方式進行賦值操作時的行為。它是可變容器類型必須實現的一個方法,同樣應該在合適的時候產生 KeyError
和 TypeError
異常。
__iter__(self, key)
它應該返回當前容器的一個迭代器。迭代器以一連串內容的形式返回,最常見的是使用 iter()
函數調用,以及在類似 for x in container:
的循環中被調用。迭代器是他們自己的對象,需要定義 __iter__
方法并在其中返回自己。
__reversed__(self)
定義了對容器使用 reversed()
內建函數時的行為。它應該返回一個反轉之后的序列。當你的序列類是有序時,類似列表和元組,再實現這個方法,
__contains__(self, item)
__contains__
定義了使用 in
和 not in
進行成員測試時類的行為。你可能好奇為什么這個方法不是序列協議的一部分,原因是,如果 __contains__
沒有定義,Python就會迭代整個序列,如果找到了需要的一項就返回 True
。
__missing__(self ,key)
__missing__
在字典的子類中使用,它定義了當試圖訪問一個字典中不存在的鍵時的行為(目前為止是指字典的實例,例如我有一個字典 d
, "george"
不是字典中的一個鍵,當試圖訪問 d["george']
時就會調用 d.__missing__("george")
)。
讓我們來看一個實現了一些函數式結構的列表,可能在其他語言中這種結構更常見(例如Haskell)::
class FunctionalList:
'''一個列表的封裝類,實現了一些額外的函數式
方法,例如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):
# 如果鍵的類型或值不合法,列表會返回異常
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.valuse[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]
就是這些,一個(微不足道的)有用的例子,向你展示了如何實現自己的序列。當然啦,自定義序列有更大的用處,而且絕大部分都在標準庫中實現了(Python是自帶電池的,記得嗎?),像 Counter
, OrderedDict
和 NamedTuple
。
你可以通過定義魔法方法來控制用于反射的內建函數 isinstance
和 issubclass
的行為。下面是對應的魔法方法:
__instancecheck__(self, instance)
檢查一個實例是否是你定義的類的一個實例(例如 isinstance(instance, class)
)。
__subclasscheck__(self, subclass)
檢查一個類是否是你定義的類的子類(例如 issubclass(subclass, class)
)。
這幾個魔法方法的適用范圍看起來有些窄,事實也正是如此。我不會在反射魔法方法上花費太多時間,因為相比其他魔法方法它們顯得不是很重要。但是它們展示了在Python中進行面向對象編程(或者總體上使用Python進行編程)時很重要的一點:不管做什么事情,都會有一個簡單方法,不管它常用不常用。這些魔法方法可能看起來沒那么有用,但是當你真正需要用到它們的時候,你會感到很幸運,因為它們還在那兒(也因為你閱讀了這本指南!)
請參考 http://docs.python.org/2/library/abc.html.
你可能已經知道了,在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, y
self.size = size
def __call__(self, x, y):
'''改變實體的位置'''
self.x, self.y = x, y
在Python 2.5中引入了一個全新的關鍵詞,隨之而來的是一種新的代碼復用方法—— with
聲明。上下文管理的概念在Python中并不是全新引入的(之前它作為標準庫的一部分實現),直到PEP 343被接受,它才成為一種一級的語言結構。可能你已經見過這種寫法了::
with open('foo.txt') as bar:
# 使用bar進行某些操作
當對象使用 with
聲明創建時,上下文管理器允許類做一些設置和清理工作。上下文管理器的行為由下面兩個魔法方法所定義:
__enter__(self)
定義使用 with
聲明創建的語句塊最開始上下文管理器應該做些什么。注意 __enter__
的返回值會賦給 with
聲明的目標,也就是 as
之后的東西。
__exit__(self, exception_type, exception_value, traceback)
定義當 with
聲明語句塊執行完畢(或終止)時上下文管理器的行為。它可以用來處理異常,進行清理,或者做其他應該在語句塊結束之后立刻執行的工作。如果語句塊順利執行, exception_type
, exception_value
和 traceback
會是 None
。否則,你可以選擇處理這個異常或者讓用戶來處理。如果你想處理異常,確保 __exit__
在完成工作之后返回 True
。如果你不想處理異常,那就讓它發生吧。
對一些具有良好定義的且通用的設置和清理行為的類,__enter__
和 __exit__
會顯得特別有用。你也可以使用這幾個方法來創建通用的上下文管理器,用來包裝其他對象。下面是一個例子::
class Closer:
'''一個上下文管理器,可以在with語句中
使用close()自動關閉對象'''
def __init__(self, obj):
self.obj = obj
def __enter__(self, obj):
return self.obj # 綁定到目標
def __exit__(self, exception_type, exception_value, traceback):
try:
self.obj.close()
except AttributeError: # obj不是可關閉的
print 'Not closable.'
return True # 成功地處理了異常
這是一個 Closer
在實際使用中的例子,使用一個FTP連接來演示(一個可關閉的socket)::
>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
... conn.dir()
...
# 為了簡單,省略了某些輸出
>>> conn.dir()
# 很長的 AttributeError 信息,不能使用一個已關閉的連接
>>> with Closer(int(5)) as i:
... i += 1
...
Not closable.
>>> i
6
看到我們的包裝器是如何同時優雅地處理正確和不正確的調用了嗎?這就是上下文管理器和魔法方法的力量。Python標準庫包含一個 contextlib
模塊,里面有一個上下文管理器 contextlib.closing()
基本上和我們的包裝器完成的是同樣的事情(但是沒有包含任何當對象沒有close()方法時的處理)。
描述符是一個類,當使用取值,賦值和刪除 時它可以改變其他對象。描述符不是用來單獨使用的,它們需要被一個擁有者類所包含。描述符可以用來創建面向對象數據庫,以及創建某些屬性之間互相依賴的類。描述符在表現具有不同單位的屬性,或者需要計算的屬性時顯得特別有用(例如表現一個坐標系中的點的類,其中的距離原點的距離這種屬性)。
要想成為一個描述符,一個類必須具有實現 __get__
, __set__
和 __delete__
三個方法中至少一個。
讓我們一起來看一看這些魔法方法:
__get__(self, instance, owner)
定義當試圖取出描述符的值時的行為。 instance
是擁有者類的實例, owner
是擁有者類本身。
__set__(self, instance, owner)
定義當描述符的值改變時的行為。 instance
是擁有者類的實例, value
是要賦給描述符的值。
__delete__(self, instance, owner)
定義當描述符的值被刪除時的行為。 instance
是擁有者類的實例
現在,來看一個描述符的有效應用:單位轉換::
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):
'''用于描述距離的類,包含英尺和米兩個描述符。'''
meter = Meter()
foot = Foot()
有些時候,特別是處理可變對象時,你可能想拷貝一個對象,改變這個對象而不影響原有的對象。這時就需要用到Python的 copy
模塊了。然而(幸運的是),Python模塊并不具有感知能力,
因此我們不用擔心某天基于Linux的機器人崛起。但是我們的確需要告訴Python如何有效率地拷貝對象。
__copy__(self)
定義對類的實例使用 copy.copy()
時的行為。 copy.copy()
返回一個對象的淺拷貝,這意味著拷貝出的實例是全新的,然而里面的數據全都是引用的。也就是說,對象本身是拷貝的,但是它的數據還是引用的(所以淺拷貝中的數據更改會影響原對象)。
__deepcopy__(self, memodict=)
定義對類的實例使用 copy.deepcopy()
時的行為。 copy.deepcopy()
返回一個對象的深拷貝,這個對象和它的數據全都被拷貝了一份。 memodict
是一個先前拷貝對象的緩存,它優化了拷貝過程,而且可以防止拷貝遞歸數據結構時產生無限遞歸。當你想深拷貝一個單獨的屬性時,在那個屬性上調用 copy.deepcopy()
,使用 memodict
作為第一個參數。
這些魔法方法有什么用武之地呢?像往常一樣,當你需要比默認行為更加精確的控制時。例如,如果你想拷貝一個對象,其中存儲了一個字典作為緩存(可能會很大),拷貝緩存可能是沒有意義的。如果這個緩存可以在內存中被不同實例共享,那么它就應該被共享。
如果你和其他的Python愛好者共事過,很可能你已經聽說過Pickling了。Pickling是Python數據結構的序列化過程,當你想存儲一個對象稍后再取出讀取時,Pickling會顯得十分有用。然而它同樣也是擔憂和混淆的主要來源。
Pickling是如此的重要,以至于它不僅僅有自己的模塊( pickle
),還有自己的協議和魔法方法。首先,我們先來簡要的介紹一下如何pickle已存在的對象類型(如果你已經知道了,大可跳過這部分內容)。
我們一起來pickle吧。假設你有一個字典,你想存儲它,稍后再取出來。你可以把它的內容寫入一個文件,小心翼翼地確保使用了正確地格式,要把它讀取出來,你可以使用 exec()
或處理文件輸入。但是這種方法并不可靠:如果你使用純文本來存儲重要數據,數據很容易以多種方式被破壞或者修改,導致你的程序崩潰,更糟糕的情況下,還可能在你的計算機上運行惡意代碼。因此,我們要pickle它::
import pickle
data = {'foo': [1,2,3],
'bar': ('Hello', 'world!'),
'baz': True}
jar = open('data.pkl', 'wb')
pickle.dump(data, jar) # 將pickle后的數據寫入jar文件
jar.close()
過了幾個小時,我們想把它取出來,我們只需要反pickle它::
import pickle
pkl_file = open('data.pkl', 'rb') # 與pickle后的數據連接
data = pickle.load(pkl_file) # 把它加載進一個變量
print data
pkl_file.close()
將會發生什么?正如你期待的,它就是我們之前的 data
。
現在,還需要謹慎地說一句: pickle并不完美。Pickle文件很容易因為事故或被故意的破壞掉。Pickling或許比純文本文件安全一些,但是依然有可能被用來運行惡意代碼。而且它還不支持跨Python版本,所以不要指望分發pickle對象之后所有人都能正確地讀取。然而不管怎么樣,它依然是一個強有力的工具,可以用于緩存和其他類型的持久化工作。
Pickle不僅僅可以用于內建類型,任何遵守pickle協議的類都可以被pickle。Pickle協議有四個可選方法,可以讓類自定義它們的行為(這和C語言擴展略有不同,那不在我們的討論范圍之內)。
__getinitargs__(self)
如果你想讓你的類在反pickle時調用 __init__
,你可以定義 __getinitargs__(self)
,它會返回一個參數元組,這個元組會傳遞給 __init__
。注意,這個方法只能用于舊式類。
__getnewargs__(self)
對新式類來說,你可以通過這個方法改變類在反pickle時傳遞給 __new__
的參數。這個方法應該返回一個參數元組。
__getstate__(self)
你可以自定義對象被pickle時被存儲的狀態,而不使用對象的 __dict__
屬性。 這個狀態在對象被反pickle時會被 __setstate__
使用。
__setstate__(self)
當一個對象被反pickle時,如果定義了 __setstate__
,對象的狀態會傳遞給這個魔法方法,而不是直接應用到對象的 __dict__
屬性。這個魔法方法和 __getstate__
相互依存:當這兩個方法都被定義時,你可以在Pickle時使用任何方法保存對象的任何狀態。
__reduce__(self)
當定義擴展類型時(也就是使用Python的C語言API實現的類型),如果你想pickle它們,你必須告訴Python如何pickle它們。 __reduce__
被定義之后,當對象被Pickle時就會被調用。它要么返回一個代表全局名稱的字符串,Pyhton會查找它并pickle,要么返回一個元組。這個元組包含2到5個元素,其中包括:一個可調用的對象,用于重建對象時調用;一個參數元素,供那個可調用對象使用;被傳遞給 __setstate__
的狀態(可選);一個產生被pickle的列表元素的迭代器(可選);一個產生被pickle的字典元素的迭代器(可選);
__reduce_ex__(self)
__reduce_ex__
的存在是為了兼容性。如果它被定義,在pickle時 __reduce_ex__
會代替 __reduce__
被調用。 __reduce__
也可以被定義,用于不支持 __reduce_ex__
的舊版pickle的API調用。
我們的例子是 Slate
,它會記住它的值曾經是什么,以及那些值是什么時候賦給它的。然而
每次被pickle時它都會變成空白,因為當前的值不會被存儲::
import time
class Slate:
'''存儲一個字符串和一個變更日志的類
每次被pickle都會忘記它當前的值'''
def __init__(self, value):
self.value = value
self.last_change = time.asctime()
self.history = {}
def change(self, new_value):
# 改變當前值,將上一個值記錄到歷史
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時得到一個空白的slate
return self.history
def __setstate__(self):
# 使self.history = slate,last_change
# 和value為未定義
self.history = state
self.value, self.last_change = None, None
這本指南的目標是使所有閱讀它的人都能有所收獲,無論他們有沒有使用Python或者進行面向對象編程的經驗。如果你剛剛開始學習Python,你會得到寶貴的基礎知識,了解如何寫出具有豐富特性的,優雅而且易用的類。如果你是中級的Python程序員,你或許能掌握一些新的概念和技巧,以及一些可以減少代碼行數的好辦法。如果你是專家級別的Python愛好者,你又重新復習了一遍某些可能已經忘掉的知識,也可能順便了解了一些新技巧。無論你的水平怎樣,我希望這趟遨游Python特殊方法的旅行,真的對你產生了魔法般的效果(實在忍不住不說最后這個雙關)。
一些魔法方法直接和內建函數對應,這種情況下,如何調用它們是顯而易見的。然而,另外的情況下,調用魔法方法的途徑并不是那么明顯。這個附錄旨在展示那些不那么明顯的調用魔法方法的語法。
魔法方法 | 什么時候被調用 | 解釋 |
---|---|---|
new(cls [,...]) | instance=MyClass(arg1,arg2) | __new__在實例創建時調用 |
init(self [,...]) | instance=MyClass(arg1,arg2) | __init__在實例創建時調用 |
cmp(self) | 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) | self.name = val | 給屬性賦值 |
_delattr(self, name) | del self.name | 刪除屬性 |
getattribute(self,name) | self.name | 訪問任意屬性 |
getitem(self, key) | self[key] | 使用索引訪問某個元素 |
setitem(self, key) | 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進行成員測試 |
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) | Pickling |
setstate(self) | data = pickle.load(pkl_file) | Pickling |
在這里,我們記錄了幾個在對象模型方面 Python 3 和 Python 2.x 之間的主要區別。
Python 3中string和unicode的區別不復存在,因此 __unicode__
被取消了, __bytes__
加入進來(與Python 2.7 中的 __str__
和 __unicode__
行為類似),用于新的創建字節數組的內建方法。
Python 3中默認除法變成了 true 除法,因此 __div__
被取消了。
__coerce__
被取消了,因為和其他魔法方法有功能上的重復,以及本身行為令人迷惑。
__cmp__
被取消了,因為和其他魔法方法有功能上的重復。
__nonzero__
被重命名成 __bool__
。免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。