Python中特殊方法的分類與總結
以下為轉載:
Python 用下劃線作為變數字首和字尾指定特殊變數
_xxx 不能用’from module import *’匯入
__xxx__ 系統定義名字
__xxx 類中的私有變數名
核心風格:避免用下劃線作為變數名的開始。
因為下劃線對直譯器有特殊的意義,而且是內建識別符號所使用的符號,我們建議程式設計師避免用下劃線作為變數名的開始。一般來講,變數名_xxx被看作是“私有的”,在模組或類外不可以使用。當變數是私有的時候,用_xxx 來表示變數是很好的習慣。因為變數名__xxx__對Python 來說有特殊含義,對於普通的變數應當避免這種命名風格。
“單下劃線” 開始的成員變數叫做保護變數,意思是隻有類物件和子類物件自己能訪問到這些變數;
“雙下劃線” 開始的是私有成員,意思是隻有類物件自己能訪問,連子類物件也不能訪問到這個資料。
以單下劃線開頭(_foo)的代表不能直接訪問的類屬性,需通過類提供的介面進行訪問,不能用“from xxx import *”而匯入;以雙下劃線開頭的(__foo)代表類的私有成員;以雙下劃線開頭和結尾的(__foo__)代表python裡特殊方法專用的標識,如 __init__()代表類的建構函式。
現在我們來總結下所有的系統定義屬性和方法, 先來看下保留屬性:
>>> Class1.__doc__ # 型別幫助資訊 'Class1 Doc.'
>>> Class1.__name__ # 型別名稱 'Class1'
>>> Class1.__module__ # 型別所在模組 '__main__'
>>> Class1.__bases__ # 型別所繼承的基類 (<type 'object'>,)
>>> Class1.__dict__ # 型別字典,儲存所有型別成員資訊。 <dictproxy object at 0x00D3AD70>
>>> Class1().__class__ # 型別 <class '__main__.Class1'>
>>> Class1().__module__ # 例項型別所在模組 '__main__'
>>> Class1().__dict__ # 物件字典,儲存所有例項成員資訊。 {'i': 1234}
接下來是保留方法,可以把保留方法分類:
類的基礎方法
序號 | 目的 | 所編寫程式碼 | Python 實際呼叫 |
---|---|---|---|
① | 初始化一個例項 | x = MyClass() | x.__init__() |
② | 字串的“官方”表現形式 | repr(x) | x.__repr__() |
③ | 字串的“非正式”值 | str(x) | x.__str__() |
④ | 位元組陣列的“非正式”值 | bytes(x) | x.__bytes__() |
⑤ | 格式化字串的值 | format(x, format_spec) | x.__format__(format_spec) |
- 對
__init__()
方法的呼叫發生在例項被建立 之後 。如果要控制實際建立程序,請使用__new__()
方法。 - 按照約定,
__repr__()
方法所返回的字串為合法的 Python 表示式。 - 在呼叫
print(x)
的同時也呼叫了__str__()
方法。 - 由於
bytes
型別的引入而從 Python 3 開始出現。
行為方式與迭代器類似的類
序號 | 目的 | 所編寫程式碼 | Python 實際呼叫 |
---|---|---|---|
① | 遍歷某個序列 | iter(seq) | seq.__iter__() |
② | 從迭代器中獲取下一個值 | next(seq) | seq.__next__() |
③ | 按逆序建立一個迭代器 | reversed(seq) | seq.__reversed __() |
- 無論何時建立迭代器都將呼叫
__iter__()
方法。這是用初始值對迭代器進行初始化的絕佳之處。 - 無論何時從迭代器中獲取下一個值都將呼叫
__next__()
方法。 __reversed__()
方法並不常用。它以一個現有序列為引數,並將該序列中所有元素從尾到頭以逆序排列生成一個新的迭代器。
計算屬性
序號 | 目的 | 所編寫程式碼 | Python 實際呼叫 |
---|---|---|---|
① | 獲取一個計算屬性(無條件的) | x.my_property | x.__getattribute__('my_property') |
② | 獲取一個計算屬性(後備) | x.my_property | x.__getattr__('my_property') |
③ | 設定某屬性 | x.my_property = value | x.__setattr__('my_property',value) |
④ | 刪除某屬性 | del x.my_property | x.__delattr__('my_property') |
⑤ | 列出所有屬性和方法 | dir(x) | x.__dir__() |
- 如果某個類定義了
__getattribute__()
方法,在 每次引用屬性或方法名稱時Python 都呼叫它(特殊方法名稱除外,因為那樣將會導致討厭的無限迴圈)。 - 如果某個類定義了
__getattr__()
方法,Python 將只在正常的位置查詢屬性時才會呼叫它。如果例項 x 定義了屬性color,x.color
將 不會 呼叫x.__getattr__('color')
;而只會返回x.color已定義好的值。 - 無論何時給屬性賦值,都會呼叫
__setattr__()
方法。 - 無論何時刪除一個屬性,都將呼叫
__delattr__()
方法。 - 如果定義了
__getattr__()
或__getattribute__()
方法,__dir__()
方法將非常有用。通常,呼叫dir(x)
將只顯示正常的屬性和方法。如果__getattr()__
方法動態處理color 屬性,dir(x)
將不會將 color 列為可用屬性。可通過覆蓋__dir__()
方法允許將color 列為可用屬性,對於想使用你的類但卻不想深入其內部的人來說,該方法非常有益。
行為方式與函式類似的類
可以讓類的例項變得可呼叫——就像函式可以呼叫一樣——通過定義 __call__()
方法。
序號 | 目的 | 所編寫程式碼 | Python 實際呼叫 |
---|---|---|---|
① | 像呼叫函式一樣“呼叫”一個例項 | my_instance() | my_instance.__call__() |
zipfile
模組 通過該方式定義了一個可以使用給定密碼解密 經加密 zip 檔案的類。該 zip解密 演算法需要在解密的過程中儲存狀態。通過將解密器定義為類,使我們得以在 decryptor 類的單個例項中對該狀態進行維護。狀態在__init__()
方法中進行初始化,如果檔案 經加密 則進行更新。但由於該類像函式一樣“可呼叫”,因此可以將例項作為map()
函式的第一個引數傳入,程式碼如下:
# excerpt from zipfile.py class _ZipDecrypter: def __init__(self, pwd): self.key0 = 305419896 self.key1 = 591751049 self.key2 = 878082192 for p in pwd: self._UpdateKeys(p) def __call__(self, c): assert isinstance(c, int) k = self.key2 | 2 c = c ^ (((k * (k^1)) >> & 255) self._UpdateKeys(c) return c zd = _ZipDecrypter(pwd) bytes = zef_file.read(12) h = list(map(zd, bytes[0:12]))
_ZipDecryptor
類維護了以三個旋轉金鑰形式出現的狀態,該狀態稍後將在_UpdateKeys()
方法中更新(此處未展示)。- 該類定義了一個
__call__()
方法,使得該類可像函式一樣呼叫。在此例中,__call__()
對 zip 檔案的單個位元組進行解密,然後基於經解密的位元組對旋轉密碼進行更新。 - zd 是
_ZipDecryptor
類的一個例項。變數 pwd 被傳入__init__()
方法,並在其中被儲存和用於首次旋轉密碼更新。 - 給出 zip 檔案的頭 12 個位元組,將這些位元組對映給 zd 進行解密,實際上這將導致呼叫
__call__()
方法 12 次,也就是 更新內部狀態並返回結果位元組 12 次。
行為方式與序列類似的類
如果類作為一系列值的容器出現——也就是說如果對某個類來說,是否“包含”某值是件有意義的事情——那麼它也許應該定義下面的特殊方法已,讓它的行為方式與序列類似。
序號 | 目的 | 所編寫程式碼 | Python 實際呼叫 |
---|---|---|---|
① | 序列的長度 | len(seq) | seq.__len__() |
② | 瞭解某序列是否包含特定的值 | x in seq | seq.__contains__(x) |
cgi
模組 在其FieldStorage
類中使用了這些方法,該類用於表示提交給動態網頁的所有表單欄位或查詢引數。
# A script which responds to http://example.com/search?q=cgi import cgi fs = cgi.FieldStorage() if 'q' in fs: do_search() # An excerpt from cgi.py that explains how that works class FieldStorage: . . . def __contains__(self, key): if self.list is None: raise TypeError('not indexable') return any(item.name == key for item in self.list) def __len__(self): return len(self.keys())
一旦建立了cgi.FieldStorage
類的例項,就可以使用 “in
” 運算子來檢查查詢字串中是否包含了某個特定引數。
- 而
__contains__()
方法是令該魔法生效的主角。 - 如果程式碼為
if 'q' in fs
,Python 將在 fs 物件中查詢__contains__()
方法,而該方法在cgi.py
中已經定義。'q'
的值被當作key 引數傳入__contains__()
方法。 - 同樣的
FieldStorage
類還支援返回其長度,因此可以編寫程式碼len(fs)
而其將呼叫FieldStorage
的__len__()
方法,並返回其識別的查詢引數個數。 self.keys()
方法檢查self.list is None
是否為真值,因此__len__
方法無需重複該錯誤檢查。
行為方式與字典類似的類
在前一節的基礎上稍作拓展,就不僅可以對 “in
” 運算子和 len()
函式進行響應,還可像全功能字典一樣根據鍵來返回值。
序號 | 目的 | 所編寫程式碼 | Python 實際呼叫 |
---|---|---|---|
① | 通過鍵來獲取值 | x[key] | x.__getitem__(key) |
② | 通過鍵來設定值 | x[key] = value | x.__setitem__(key, value) |
③ | 刪除一個鍵值對 | del x[key] | x.__delitem__(key) |
④ | 為缺失鍵提供預設值 | x[nonexistent_key] | x.__missing__(nonexistent_key) |
cgi
模組 的FieldStorage
類 同樣定義了這些特殊方法,也就是說可以像下面這樣編碼:
# A script which responds to http://example.com/search?q=cgi import cgi fs = cgi.FieldStorage() if 'q' in fs: do_search(fs['q']) # An excerpt from cgi.py that shows how it works class FieldStorage: . . .
def __getitem__(self, key): if self.list is None: raise TypeError('not indexable') found = [] for item in self.list: if item.name == key: found.append(item) if not found: raise KeyError(key) if len(found) == 1: return found[0] else: return found
- fs 物件是
cgi.FieldStorage
類的一個例項,但仍然可以像fs['q']
這樣估算表示式。 fs['q']
將 key 引數設定為'q'
來呼叫__getitem__()
方法。然後它將在其內部維護的查詢引數列表 (self.list) 中查詢一個.name
與給定鍵相符的字典項。
可比較的類
我將此內容從前一節中拿出來使其單獨成節,是因為“比較”操作並不侷限於數字。許多資料型別都可以進行比較——字串、列表,甚至字典。如果要建立自己的類,且物件之間的比較有意義,可以使用下面的特殊方法來實現比較。
序號 | 目的 | 所編寫程式碼 | Python 實際呼叫 |
---|---|---|---|
① | 相等 | x == y | x.__eq__(y) |
② | 不相等 | x != y | x.__ne__(y) |
③ | 小於 | x < y | x.__lt__(y) |
④ | 小於或等於 | x <= y | x.__le__(y) |
⑤ | 大於 | x > y | x.__gt__(y) |
⑥ | 大於或等於 | x >= y | x.__ge__(y) |
⑦ | 布林上上下文環境中的真值 | if x: | x.__bool__() |
☞如果定義了
__lt__()
方法但沒有定義__gt__()
方法,Python 將通過經交換的運算元呼叫__lt__()
方法。然而,Python 並不會組合方法。例如,如果定義了__lt__()
方法和__eq()__
方法,並試圖測試是否x <= y
,Python 不會按順序呼叫__lt__()
和__eq()__
。它將只調用__le__()
方法。
可序列化的類
Python 支援 任意物件的序列化和反序列化。(多數 Python 參考資料稱該過程為 “pickling” 和 “unpickling”)。該技術對與將狀態儲存為檔案並在稍後恢復它非常有意義。所有的內建資料型別 均已支援 pickling 。如果建立了自定義類,且希望它能夠 pickle,閱讀pickle 協議 瞭解下列特殊方法何時以及如何被呼叫。
序號 | 目的 | 所編寫程式碼 | Python 實際呼叫 |
---|---|---|---|
① | 自定義物件的複製 | copy.copy(x) | x.__copy__() |
② | 自定義物件的深度複製 | copy.deepcopy(x) | x.__deepcopy__() |
③ | 在 pickling 之前獲取物件的狀態 | pickle.dump(x, file) | x.__getstate__() |
④ | 序列化某物件 | pickle.dump(x, file) | x.__reduce__() |
⑤ | 序列化某物件(新 pickling 協議) | pickle.dump(x, file, protocol_version) | x.__reduce_ex__(protocol_version) |
⑥ | 控制 unpickling 過程中物件的建立方式 | x = pickle.load(file) | x.__getnewargs__() |
⑦ | 在 unpickling 之後還原物件的狀態 | x = pickle.load(file) | x.__setstate__() |
* 要重建序列化物件,Python 需要建立一個和被序列化的物件看起來一樣的新物件,然後設定新物件的所有屬性。__getnewargs__()
方法控制新物件的建立過程,而__setstate__()
方法控制屬性值的還原方式。
可在 with
語塊中使用的類
with
語塊定義了 執行時刻上下文環境;在執行 with
語句時將“進入”該上下文環境,而執行該語塊中的最後一條語句將“退出”該上下文環境。
序號 | 目的 | 所編寫程式碼 | Python 實際呼叫 |
---|---|---|---|
① | 在進入 with 語塊時進行一些特別操作 | with x: | x.__enter__() |
② | 在退出 with 語塊時進行一些特別操作 | with x: | x.__exit__() |
以下是 with file
習慣用法 的運作方式:
# excerpt from io.py: def _checkClosed(self, msg=None): '''Internal: raise an ValueError if file is closed ''' if self.closed: raise ValueError('I/O operation on closed file.' if msg is None else msg) def __enter__(self): '''Context management protocol. Returns self.''' self._checkClosed() return self def __exit__(self, *args): '''Context management protocol. Calls close()''' self.close()
- 該檔案物件同時定義了一個
__enter__()
和一個__exit__()
方法。該__enter__()
方法檢查檔案是否處於開啟狀態;如果沒有,_checkClosed()
方法引發一個例外。 __enter__()
方法將始終返回 self —— 這是with
語塊將用於呼叫屬性和方法的物件- 在
with
語塊結束後,檔案物件將自動關閉。怎麼做到的?在__exit__()
方法中呼叫了self.close()
.
☞該
__exit__()
方法將總是被呼叫,哪怕是在with
語塊中引發了例外。實際上,如果引發了例外,該例外資訊將會被傳遞給__exit__()
方法。查閱With 狀態上下文環境管理器 瞭解更多細節。
真正神奇的東西
如果知道自己在幹什麼,你幾乎可以完全控制類是如何比較的、屬性如何定義,以及類的子類是何種型別。
序號 | 目的 | 所編寫程式碼 | Python 實際呼叫 |
---|---|---|---|
① | 類構造器 | x = MyClass() | x.__new__() |
② | 類析構器 | del x | x.__del__() |
③ | 只定義特定集合的某些屬性 | x.__slots__() | |
④ | 自定義雜湊值 | hash(x) | x.__hash__() |
⑤ | 獲取某個屬性的值 | x.color | type(x).__dict__['color'].__get__(x, type(x)) |
⑥ | 設定某個屬性的值 | x.color = 'PapayaWhip' | type(x).__dict__['color'].__set__(x, 'PapayaWhip') |
⑦ | 刪除某個屬性 | del x.color | type(x).__dict__['color'].__del__(x) |
⑧ | 控制某個物件是否是該物件的例項 your class | isinstance(x, MyClass) | MyClass.__instancecheck__(x) |
⑨ | 控制某個類是否是該類的子類 | issubclass(C, MyClass) | MyClass.__subclasscheck__(C) |
⑩ | 控制某個類是否是該抽象基類的子類 | issubclass(C, MyABC) | MyABC.__subclasshook__(C ) |