介紹
在Python中,所有以“”雙底線包起來的方法,都統稱為“Magic Method”,例如類的初始化方法 init ,Python中所有的魔術方法均在官方文檔中有相應描述,但是對於官方的描述比較混亂而且組織比較鬆散。很難找到有一個例子。
構造和初始化
每個Pythoner都知道一個最基本的魔術方法, init 。通過此方法我們可以定義一個對象的初始操作。然而,當調用 x = SomeClass() 的時候, init 並不是第一個被調用的方法。實際上,還有一個叫做new 的方法,兩個共同構成了“建構函式”。
new是用來建立類並返回這個類的執行個體, 而init只是將傳入的參數來初始化該執行個體。
在對象生命週期調用結束時,del 方法會被調用,可以將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的人會抱怨它缺乏類的真正封裝。(沒有辦法定義私人變數,然後定義公用的getter和setter)。Python其實可以通過魔術方法來完成封裝。我們來看一下:
定義當使用者試圖擷取一個不存在的屬性時的行為。這適用於對普通拼字錯誤的擷取和重新導向,對擷取一些不建議的屬性時候給出警告(如果你願意你也可以計算並且給出一個值)或者處理一個 AttributeError 。只有當調用不存在的屬性的時候會被返回。
與getattr(self, name)不同,setattr 是一個封裝的解決方案。無論屬性是否存在,它都允許你定義對對屬性的賦值行為,以為這你可以對屬性的值進行個性定製。實現setattr時要避免"無限遞迴"的錯誤。
與 setattr 相同,但是功能是刪除一個屬性而不是設定他們。實現時也要防止無限遞迴現象發生。
getattribute定義了你的屬性被訪問時的行為,相比較,getattr只有該屬性不存在時才會起作用。因此,在支援getattribute的Python版本,調用getattr前必定會調用 getattribute。getattribute同樣要避免"無限遞迴"的錯誤。需要提醒的是,最好不要嘗試去實現getattribute,因為很少見到這種做法,而且很容易出bug。
在進行屬性存取控制定義的時候很可能會很容易引起“無限遞迴”。如下面代碼:
# 錯誤用法def setattr(self, name, value): self.name = value # 每當屬性被賦值的時候(如self.name = value), ``setattr()`` 會被調用,這樣就造成了遞迴調用。 # 這意味這會調用 ``self.setattr('name', value)`` ,每次方法會調用自己。這樣會造成程式崩潰。# 正確用法def setattr(self, name, value): self.dict[name] = value # 給類中的屬性名稱分配值 # 定製特有屬性
Python的魔術方法很強大,但是用時卻需要慎之又慎,瞭解正確的使用方法非常重要。
建立自訂容器
有很多方法可以讓你的Python類行為向內建容器類型一樣,比如我們常用的list、dict、tuple、string等等。Python的容器類型分為可變類型(如list、dict)和不可變類型(如string、tuple),可變容器和不可變容器的區別在於,不可變容器一旦賦值後,不可對其中的某個元素進行修改。
在講建立自訂容器之前,應該先瞭解下協議。這裡的協議跟其他語言中所謂的"介面"概念很像,它給你很多你必須定義的方法。然而在Python中的協議是很不正式的,不需要明確聲明實現。事實上,他們更像一種指南。
自訂容器的magic method
下面細緻瞭解下定義容器可能用到的魔術方法。首先,實現不可變容器的話,你只能定義 len 和 getitem (下面會講更多)。可變容器協議則需要所有不可變容器的所有,另外還需要 setitem 和 delitem 。如果你希望你的對象是可迭代的話,你需要定義 iter 會返回一個迭代器。迭代器必須遵循迭代器協議,需要有 iter(返回它本身) 和 next。
返回容器的長度。對於可變和不可變容器的協議,這都是其中的一部分。
定義當某一項被訪問時,使用self[key]所產生的行為。這也是不可變容器和可變容器協議的一部分。如果鍵的類型錯誤將產生TypeError;如果key沒有合適的值則產生KeyError。
當你執行self[key] = value時,調用的是該方法。
定義當一個項目被刪除時的行為(比如 del self[key])。這隻是可變容器協議中的一部分。當使用一個無效的鍵時應該拋出適當的異常。
返回一個容器迭代器,很多情況下會返回迭代器,尤其是當內建的iter()方法被調用的時候,以及當使用for x in container:方式迴圈的時候。迭代器是它們本身的對象,它們必須定義返回self的iter方法。
實現當reversed()被調用時的行為。應該返回序列反轉後的版本。僅當序列可以是有序的時候實現它,例如對於列表或者元組。
定義了調用in和not in來測試成員是否存在的時候所產生的行為。你可能會問為什麼這個不是序列協議的一部分?因為當contains沒有被定義的時候,如果沒有定義,那麼Python會迭代容器中的元素來一個一個比較,從而決定返回True或者False。
dict字典類型會有該方法,它定義了key如果在容器中找不到時觸發的行為。比如d = {'a': 1}, 當你執行d[notexist]時,d.missing['notexist']就會被調用。
一個列子
下面是書中的例子,用魔術方法來實現Haskell語言中的一個資料結構。
# -*- coding: utf-8 -*-class FunctionalList: ''' 實現了內建類型list的功能,並豐富了一些其他方法: 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 FunctionalList(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]
其實在collections模組中已經有了很多類似的實現,比如Counter、OrderedDict等等。
反射
你也可以控制怎麼使用內建在函數sisinstance()和issubclass()方法 反射定義魔術方法. 這個魔術方法是:
檢查一個執行個體是不是你定義的類的執行個體
這些魔術方法的用例看起來很小, 並且確實非常實用. 它們反應了關於物件導向程式上一些重要的東西在Python上,並且總的來說Python: 總是一個簡單的方法去找某些事情, 即使是沒有必要的. 這些魔法方法可能看起來不是很有用, 但是一旦你需要它們,你會感到慶幸它們的存在。
可調用的對象
你也許已經知道,在Python中,方法是最進階的對象。這意味著他們也可以被傳遞到方法中,就像其他對象一樣。這是一個非常驚人的特性。
在Python中,一個特殊的魔術方法可以讓類的執行個體的行為表現的像函數一樣,你可以調用它們,將一個函數當做一個參數傳到另外一個函數中等等。這是一個非常強大的特性,其讓Python編程更加舒適甜美。
允許一個類的執行個體像函數一樣被調用。實質上說,這意味著 x() 與 x.call() 是相同的。注意 call 的參數可變。這意味著你可以定義 call 為其他你想要的函數,無論有多少個參數。
call 在那些類的執行個體經常改變狀態的時候會非常有效。調用這個執行個體是一種改變這個對象狀態的直接和優雅的做法。用一個執行個體來表達最好不過了:
# -*- coding: UTF-8 -*-class Entity: """ 調用實體來改變實體的位置 """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
上下文管理
with聲明是從Python2.5開始引進的關鍵詞。你應該遇過這樣子的代碼:
with open('foo.txt') as bar: # do something with bar
在with聲明的程式碼片段中,我們可以做一些對象的開始操作和退出操作,還能對異常進行處理。這需要實現兩個魔術方法: enter 和 exit。
定義了當使用with語句的時候,會話管理器在塊被初始建立時要產生的行為。請注意,enter的傳回值與with語句的目標或者as後的名字綁定。
定義了當一個代碼塊被執行或者終止後,會話管理器應該做什麼。它可以被用來處理異常、執行清理工作或做一些代碼塊執行完畢之後的日常工作。如果代碼塊執行成功,exception_type,exception_value,和traceback將會為None。否則,你可以選擇處理這個異常或者是直接交給使用者處理。如果你想處理這個異常的話,請確保exit在所有語句結束之後返回True。如果你想讓異常被會話管理器處理的話,那麼就讓其產生該異常。
建立對象描述器
描述器是通過擷取、設定以及刪除的時候被訪問的類。當然也可以改變其它的對象。描述器並不是獨立的。相反,它意味著被一個所有者類持有。當建立物件導向的資料庫或者類,裡面含有相互依賴的屬相時,描述器將會非常有用。一種典型的使用方法是用不同的單位表示同一個數值,或者表示某個資料的附加屬性。
為了成為一個描述器,一個類必須至少有get,set,delete方法被實現:
get(self, instance, owner):
定義了當描述器的值被取得的時候的行為。instance是擁有該描述器對象的一個執行個體。owner是擁有者本身
set(self, instance, value):
定義了當描述器的值被改變的時候的行為。instance是擁有該描述器類的一個執行個體。value是要設定的值。
delete(self, instance):
定義了當描述器的值被刪除的時候的行為。instance是擁有該描述器對象的一個執行個體。
下面是一個描述器的執行個體:單位轉換。
# -*- coding: UTF-8 -*-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, value): 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.2808class Distance(object): """ 用米和英寸來表示兩個描述器之間的距離 """ meter = Meter(10) foot = Foot()
使用時:
>>>d = Distance()>>>print d.foot>>>print d.meter32.80810.0
複製
有時候,尤其是當你在處理可變對象時,你可能想要複製一個對象,然後對其做出一些改變而不希望影響原來的對象。這就是Python的copy所發揮作用的地方。
定義了當對你的類的執行個體調用copy.copy()時所產生的行為。copy.copy()返回了你的對象的一個淺拷貝——這意味著,當執行個體本身是一個新執行個體時,它的所有資料都被引用了——例如,當一個對象本身被複製了,它的資料仍然是被引用的(因此,對於淺拷貝中資料的更改仍然可能導致資料在原始對象的中的改變)。
定義了當對你的類的執行個體調用copy.deepcopy()時所產生的行為。copy.deepcopy()返回了你的對象的一個深拷貝——對象和其資料都被拷貝了。memodict是對之前被拷貝的對象的一個緩衝——這最佳化了拷貝過程並且阻止了對遞迴資料結構拷貝時的無限遞迴。當你想要進行對一個單獨的屬性進行深拷貝時,調用copy.deepcopy(),並以memodict為第一個參數。
附錄
用於比較的魔術方法
| Magic method |
explain |
cmp(self, other) |
是比較方法裡面最基本的的魔法方法 |
eq(self, other) |
定義相等符號的行為,== |
ne(self,other) |
定義不等符號的行為,!= |
lt(self,other) |
定義小於符號的行為,< |
gt(self,other) |
定義大於符號的行為,> |
le(self,other) |
定義小於等於符號的行為,<= |
ge(self,other) |
定義大於等於符號的行為,>= |
數值計算的魔術方法
單目運算子和函數
| Magic method |
explain |
pos(self) |
實現一個取正數的操作 |
neg(self) |
實現一個取負數的操作 |
abs(self) |
實現一個內建的abs()函數的行為 |
invert(self) |
實現一個取反操作符(~操作符)的行為 |
round(self, n) |
實現一個內建的round()函數的行為 |
floor(self) |
實現math.floor()的函數行為 |
ceil(self) |
實現math.ceil()的函數行為 |
trunc(self) |
實現math.trunc()的函數行為 |
雙目運算子或函數
| Magic method |
explain |
add(self, other) |
實現一個加法 |
sub(self, other) |
實現一個減法 |
mul(self, other) |
實現一個乘法 |
floorp(self, other) |
實現一個“//”操作符產生的整除操作() |
p(self, other) |
實現一個“/”操作符代表的除法操作 |
truep(self, other) |
實現真實除法 |
mod(self, other) |
實現一個“%”操作符代表的模數操作 |
pmod(self, other) |
實現一個內建函數pmod() |
pow |
實現一個指數操作(“**”操作符)的行為 |
lshift(self, other) |
實現一個位左移操作(<<)的功能 |
rshift(self, other) |
實現一個位右移操作(>>)的功能 |
and(self, other) |
實現一個按位進行與操作(&)的行為 |
or(self, other) |
實現一個按位進行或操作的行為 |
xor(self, other) |
xor(self, other) |
增量運算
| Magic method |
explain |
iadd(self, other) |
加法賦值 |
isub(self, other) |
減法賦值 |
imul(self, other) |
乘法賦值 |
ifloorp(self, other) |
整除賦值,地板除,相當於 //= 運算子 |
ip(self, other) |
除法賦值,相當於 /= 運算子 |
itruep(self, other) |
真除賦值 |
imod_(self, other) |
模賦值,相當於 %= 運算子 |
ipow |
乘方賦值,相當於 **= 運算子 |
ilshift(self, other) |
左移賦值,相當於 <<= 運算子 |
irshift(self, other) |
左移賦值,相當於 >>= 運算子 |
iand(self, other) |
與賦值,相當於 &= 運算子 |
ior(self, other) |
或賦值 |
ixor(self, other) |
異或運算子,相當於 ^= 運算子 |
類型轉換
| Magic method |
explain |
int(self) |
轉換成整型 |
long(self) |
轉換成長整型 |
float(self) |
轉換成浮點型 |
complex(self) |
轉換成 複數型 |
oct(self) |
轉換成八進位 |
hex(self) |
轉換成十六進位 |
index(self) |
如果你定義了一個可能被用來做切片操作的數值型,你就應該定義index |
trunc(self) |
當 math.trunc(self) 使用時被調用trunc返回自身類型的整型截取 |
coerce(self, other) |
執行混合類型的運算 |