[轉]Python中下劃線---完全解讀

轉載自:

http://blog.163.com/jackylau_v/blog/static/175754040201182113817834/

 

 

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)
  1. __init__() 方法的調用發生在實例被創建 之後 。如果要控制實際創建進程,請使用 __new__() 方法
  2. 按照約定, __repr__() 方法所返回的字符串爲合法的 Python 表達式。
  3. 在調用 print(x) 的同時也調用了 __str__() 方法。
  4. 由於 bytes 類型的引入而從 Python 3 開始出現

行爲方式與迭代器類似的類

序號 目的 所編寫代碼 Python 實際調用
遍歷某個序列 iter(seq) seq.__iter__()
從迭代器中獲取下一個值 next(seq) seq.__next__()
按逆序創建一個迭代器 reversed(seq) seq.__reversed__()
  1. 無論何時創建迭代器都將調用 __iter__() 方法。這是用初始值對迭代器進行初始化的絕佳之處。
  2. 無論何時從迭代器中獲取下一個值都將調用 __next__() 方法。
  3. __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__()
  1. 如果某個類定義了 __getattribute__() 方法,在 每次引用屬性或方法名稱時 Python 都調用它(特殊方法名稱除外,因爲那樣將會導致討厭的無限循環)。
  2. 如果某個類定義了 __getattr__() 方法,Python 將只在正常的位置查詢屬性時纔會調用它。如果實例 x 定義了屬性colorx.color不會 調用x.__getattr__('color');而只會返回x.color 已定義好的值。
  3. 無論何時給屬性賦值,都會調用 __setattr__() 方法。
  4. 無論何時刪除一個屬性,都將調用 __delattr__() 方法。
  5. 如果定義了 __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]))             ④
  1. _ZipDecryptor 類維護了以三個旋轉密鑰形式出現的狀態,該狀態稍後將在 _UpdateKeys() 方法中更新(此處未展示)。
  2. 該類定義了一個 __call__() 方法,使得該類可像函數一樣調用。在此例中,__call__() 對 zip 文件的單個字節進行解密,然後基於經解密的字節對旋轉密碼進行更新。
  3. zd_ZipDecryptor 類的一個實例。變量 pwd 被傳入 __init__() 方法,並在其中被存儲和用於首次旋轉密碼更新。
  4. 給出 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())                             ⑤
  1. 一旦創建了 cgi.FieldStorage 類的實例,就可以使用 “in” 運算符來檢查查詢字符串中是否包含了某個特定參數。
  2. __contains__() 方法是令該魔法生效的主角。
  3. 如果代碼爲 if 'q' in fs,Python 將在 fs 對象中查找 __contains__() 方法,而該方法在cgi.py 中已經定義。'q' 的值被當作 key 參數傳入__contains__() 方法。
  4. 同樣的 FieldStorage 類還支持返回其長度,因此可以編寫代碼 len(fs) 而其將調用FieldStorage__len__() 方法,並返回其識別的查詢參數個數。
  5. 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
  1. fs 對象是 cgi.FieldStorage 類的一個實例,但仍然可以像 fs['q'] 這樣估算表達式。
  2. 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()                                       ③
  1. 該文件對象同時定義了一個 __enter__() 和一個 __exit__() 方法。該 __enter__() 方法檢查文件是否處於打開狀態;如果沒有, _checkClosed() 方法引發一個例外。
  2. __enter__() 方法將始終返回 self —— 這是 with 語塊將用於調用屬性和方法的對象
  3. 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)

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章