Python下劃線命名模式 - 小結(jié)以下是一個簡短的小結(jié),即“速查表”,羅列了我在本文中談到的五種Python下劃線模式的含義: 
----------------------------------------------------------------------------------------------------------------------------------------------------------------- Python 用下劃線作為變量前綴和后綴指定特殊變量/方法。 主要存在四種情形 object # public __object__ # special, python system use, user should not define like it __object # private (name mangling during runtime) _object # obey python coding convention, consider it as private 核心風格:避免用下劃線作為變量名的開始。 因為下劃線對解釋器有特殊的意義,而且是內(nèi)建標識符所使用的符號,我們建議程序員避免用下劃線作為變量名的開始。一般來講,變量名_object被看作是“私有 的”,在模塊或類外不可以使用,不能用'from moduleimport *'導(dǎo)入。當變量是私有的時候,用_object來表示變量是很好的習(xí)慣。因為變量名__object__對Python 來說有特殊含義,對于普通的變量應(yīng)當避免這種命名風格。 python有關(guān)private的描述,python中不存在protected的概念,要么是public要么就是private,但是python中的private不像C++, Java那樣,它并不是真正意義上的private,通過name mangling(名稱改編(目的就是以防子類意外重寫基類的方法或者屬性),即前面加上“單下劃線”+類名,eg:_Class__object)機制就可以訪問private了。 "單下劃線" 開始的成員變量叫做保護變量,意思是只有類對象和子類對象自己能訪問到這些變量;"雙下劃線" 開始的是私有成員,意思是只有類對象自己能訪問,連子類對象也不能訪問到這個數(shù)據(jù)。(如下列所示) 以單下劃線開頭(_foo)的代表不能直接訪問的類屬性,需通過類提供的接口進行訪問,不能用“from xxx import *”而導(dǎo)入;以雙下劃線開頭的(__foo)代表類的私有成員;以雙下劃線開頭和結(jié)尾的(__foo__)代表python里特殊方法專用的標識,如 __init__()代表類的構(gòu)造函數(shù)。 ----------------------------------------------------------------------------------------------------------------------------------------------------------------- class Foo(): def __init__(): ... def public_method(): print 'This is public method' def __fullprivate_method(): print 'This is double underscore leading method' def _halfprivate_method(): print 'This is one underscore leading method' 實例化Foo的一個對象, f = Foo() f.public_method() # OK f.__fullprivate_method() # Error occur f._halfprivate_method() # OK f._Foo__fullprivate()_method() # OK 從上面的例子可以看出,f._halfprivate_method()可以直接訪問,確實是。不過根據(jù)python的約定,應(yīng)該將其視作private,而不要在外部使用它們,(如果你非要使用也沒轍),良好的編程習(xí)慣是不要在外部使用它。同時,根據(jù)Python docs的說明,_object和__object的作用域限制在本模塊內(nèi)。 ----------------------------------------------------------------------------------------------------------------------------------------------------------------- 理解Python命名機制(單雙下劃線開頭) (轉(zhuǎn)載:http://blog.csdn.net/lanphaday) 引子 我熱情地邀請大家猜測下面這段程序的輸出: class A(object): def __init__(self): self.__private() self.public() def __private(self): print 'A.__private()' def public(self): print 'A.public()' class B(A): def __private(self): print 'B.__private()' def public(self): print 'B.public()' b = B() 初探 正確的答案是: A.__private() B.public() 如果您已經(jīng)猜對了,那么可以不看我這篇博文了。如果你沒有猜對或者心里有所疑問,那我的這篇博文正是為您所準備的。 一切由為什么會輸出“A.__private()”開始。但要講清楚為什么,我們就有必要了解一下Python的命名機制。 據(jù) Python manual,變量名(標識符)是Python的一種原子元素。當變量名被綁定到一個對象的時候,變量名就指代這個對象,就像人類社會一樣,不是嗎?當變 量名出現(xiàn)在代碼塊中,那它就是本地變量;當變量名出現(xiàn)在模塊中,它就是全局變量。模塊相信大家都有很好的理解,但代碼塊可能讓人費解些。在這里解釋一下: 代碼塊就是可作為可執(zhí)行單元的一段Python程序文本;模塊、函數(shù)體和類定義都是代碼塊。不僅如此,每一個交互腳本命令也是一個代碼塊;一個腳本文件也是一個代碼塊;一個命令行腳本也是一個代碼塊。 接下來談?wù)勛兞康目梢娦?,我們引入一個范圍的概念。范圍就是變量名在代碼塊的可見性。 如果一個代碼塊里定義本地變量,那范圍就包括這個代碼塊。如果變量定義在一個功能代碼塊里,那范圍就擴展到這個功能塊里的任一代碼塊,除非其中定義了同名 的另一變量。但定義在類中的變量的范圍被限定在類代碼塊,而不會擴展到方法代碼塊中。 迷蹤 據(jù)上節(jié)的理論,我們可以把代碼分為三個代碼塊:類A的定義、類B的定義和變量b的定義。根據(jù)類定義,我們知道代碼給類A定義了三個成員變量(Python的函數(shù)也是對象,所以成員方法稱為成員變量也行得通。);類B定義了兩個成員變量。這可以通過以下代碼驗證: >>> print '\n'.join(dir(A)) _A__private __init__ public >>> print '\n'.join(dir(B)) _A__private _B__private __init__ public 咦,為什么類A有個名為_A__private的 Attribute 呢?而且__private消失了!這就要談?wù)凱ython的私有變量軋壓了。 探究 懂Python的朋友都知道Python把以兩個或以上下劃線字符開頭且沒有以兩個或以上下劃線結(jié)尾的變量當作私有變量。私有變量會在代碼生成之前被轉(zhuǎn)換為長格式(變?yōu)楣校?。轉(zhuǎn)換機制是這樣的:在變量前端插入類名,再在前端加入一個下劃線字符。這就是所謂的私有變量軋壓(Private name mangling)。如類 A里的__private標識符將被轉(zhuǎn)換為_A__private,這就是上一節(jié)出現(xiàn)_A__private和__private消失的原因了。 再講兩點題外話: 一是因為軋壓會使標識符變長,當超過255的時候,Python會切斷,要注意因此引起的命名沖突。 二是當類名全部以下劃線命名的時候,Python就不再執(zhí)行軋壓。如: >>> class ____(object): def __init__(self): self.__method() def __method(self): print '____.__method()' >>> print '\n'.join(dir(____)) __class__ __delattr__ __dict__ __doc__ __getattribute__ __hash__ __init__ __method # 沒被軋壓 __module__ __new__ __reduce__ __reduce_ex__ __repr__ __setattr__ __str__ __weakref__ >>> obj = ____() ____.__method() >>> obj.__method() # 可以外部調(diào)用 ____.__method() 現(xiàn)在我們回過頭來看看為什么會輸出“A.__private()”吧! 真相 相信現(xiàn)在聰明的讀者已經(jīng)猜到答案了吧?如果你還沒有想到,我給你個提示:真相跟C語言里的宏預(yù)處理差不多。 因為類A定義了一個私有成員函數(shù)(變量),所以在代碼生成之前先執(zhí)行私有變量軋壓(注意到上一節(jié)標紅的那行字沒有?)。軋壓之后,類A的代碼就變成這樣了: class A(object): def __init__(self): self._A__private() # 這行變了 self.public() def _A__private(self): # 這行也變了 print 'A.__private()' def public(self): print 'A.public()' 是不是有點像C語言里的宏展開??? 因為在類B定義的時候沒有覆蓋__init__方法,所以調(diào)用的仍然是A.__init__,即執(zhí)行了self._A__private(),自然輸出“A.__private()”了。 下面的兩段代碼可以增加說服力,增進理解: >>> class C(A): def __init__(self): # 重寫 __init__ ,不再調(diào)用 self._A__private self.__private() # 這里綁定的是 _C_private self.public() def __private(self): print 'C.__private()' def public(self): print 'C.public()' >>> c = C() C.__private() C.public() ----------------------------------------------------------------------------------------------------------------------------------------------------------------- >>> class A(object): def __init__(self): self._A__private() # 調(diào)用一個沒有定義的函數(shù), Python 會把它給我的 self.public() def __private(self): print 'A.__private()' def public(self): print 'A.public()' >>>a = A() A.__private() A.public() ----------------------------------------------------------------------------------------------------------------------------------------------------------------- 特殊方法一覽表 Model 一章列出了 83 個特殊方法的名字, 其中 47 個用于實現(xiàn)算術(shù)運算、 位運算和比較操作 1. 類的基礎(chǔ)方法 | 序號 | 目的 | 所編寫代碼 | Python 實際調(diào)用 | | ① | 初始化一個實例 | x = MyClass() | x.__init__() | | ② | 字符串的 “官方” 表現(xiàn)形式 | repr(x) | x.__repr__() | | ③ | 字符串的“非正式”值 | str(x) | x.__str__() | | ④ | 字節(jié)數(shù)組的“非正式”值 | bytes(x) | x.__bytes__() | | ⑤ | 格式化字符串的值 | format(x, format_spec) | x.__format__(format_spec) |
按照約定, repr() 方法所返回的字符串為合法的 Python 表達式 在調(diào)用 print(x) 的同時也調(diào)用了 str() 方法 2.迭代枚舉 | 序號 | 目的 | 所編寫代碼 | Python 實際調(diào)用 | | ① | 遍歷某個序列 | iter(seq) | seq.__iter__() | | ② | 從迭代器中獲取下一個值 | next(seq) | seq.__next__() | | ③ | 按逆序創(chuàng)建一個迭代器 | reversed(seq) | seq.__reversed__() |
無論何時創(chuàng)建迭代器都將調(diào)用 iter() 方法。這是用初始值對迭代器進行初始化的絕佳之處。 無論何時從迭代器中獲取下一個值都將調(diào)用 next() 方法。 __reversed__() 方法并不常用。它以一個現(xiàn)有序列為參數(shù),并將該序列中所有元素從尾到頭以逆序排列生成一個新的迭代器 3. 屬性管理 | 序號 | 目的 | 所編寫代碼 | Python 實際調(diào)用 | | ① | 獲取一個計算屬性(無條件的) | x.my_property | x.__getattribute__('my_property') | | ② | 獲取一個計算屬性(后備) | x.my_property | x.__getattr__('my_property') | | ③ | 設(shè)置某屬性 | x.my_property = value | x.__setattr__('my_property',value) | | ④ | 刪除某屬性 | del x.my_property | x.__delattr__('my_property') | | ⑤ | 列出所有屬性和方法 | dir(x) | x.__dir__() |
如果某個類定義了 __getattribute__() 方法,在 每次引用屬性或方法名稱時 Python 都調(diào)用它(特殊方法名稱除外,因為那樣將會導(dǎo)致討厭的無限循環(huán))。 如果某個類定義了 __getattr__() 方法,Python 將只在正常的位置查詢屬性時才會調(diào)用它。如果實例 x 定義了屬性color, x.color 將 不會 調(diào)用 x.__getattr__('color');而只會返回x.color 已定義好的值 無論何時給屬性賦值,都會調(diào)用 __setattr__()方法 無論何時刪除一個屬性,都將調(diào)用__delattr__()方法 如果定義了 __getattr__() 或 __getattribute__() 方法, __dir__() 方法將非常有用。通常,調(diào)用 dir(x)將只顯示正常的屬性和方法。如果__getattr()__方法動態(tài)處理 color 屬性, dir(x) 將不會將 color 列為可用屬性??赏ㄟ^覆蓋 __dir__() 方法允許將 color 列為可用屬性,對于想使用你的類但卻不想深入其內(nèi)部的人來說,該方法非常有益 4. 可序列化的類 Python 支持 任意對象的序列化和反序列化。(多數(shù) Python 參考資料稱該過程為 pickling 和 unpickling)。該技術(shù)對與將狀態(tài)保存為文件并在稍后恢復(fù)它非常有意義。所有的 內(nèi)置數(shù)據(jù)類型 均已支持 pickling 。如果創(chuàng)建了自定義類,且希望它能夠 pickle,閱讀 pickle 協(xié)議 了解下列特殊方法何時以及如何被調(diào)用 | 序號 | 目的 | 所編寫代碼 | Python 實際調(diào)用 | | ① | 自定義對象的復(fù)制 | copy.copy(x) | x.__copy__() | | ② | 自定義對象的深度復(fù)制 | copy.deepcopy(x) | x.__deepcopy__() | | ③ | 在 pickling 之前獲取對象的狀態(tài) | pickle.dump(x, file) | x.__getstate__() | | ④ | 序列化某對象 | pickle.dump(x, file) | x.__reduce__() | | ⑤ | 序列化某對象(新 pickling 協(xié)議) | pickle.dump(x, file, protocol_version) | x.__reduce_ex__(protocol_version) | | ⑥ | 控制 unpickling 過程中對象的創(chuàng)建方式 | x = pickle.load(file) | x.__getnewargs__() | | ⑦ | 在 unpickling 之后還原對象的狀態(tài) | x = pickle.load(file) | x.__setstate__() |
要重建序列化對象,Python 需要創(chuàng)建一個和被序列化的對象看起來一樣的新對象,然后設(shè)置新對象的所有屬性。__getnewargs__() 方法控制新對象的創(chuàng)建過程,而 __setstate__() 方法控制屬性值的還原方式 總結(jié) 單下劃線 (_) 1、在 CPython 等解釋器中代表交互式解釋器會話中上一條執(zhí)行的語句的結(jié)果 2、作為臨時性的名稱使用,分配了一個特定的名稱但是在后面不會用到該名稱 3、用于實現(xiàn)國際化和本地化字符串之間翻譯查找的函數(shù)名稱(遵循相應(yīng)的C約定) 4、名稱前的單下劃線,用于指定該名稱屬性為 私有,這并不是語法規(guī)定而是慣例,在使用這些代碼時將大家會知道以 _ 開頭的名稱只供內(nèi)部使用,在 from <Package> import * 時,以 _ 開頭的名稱都不會被導(dǎo)入,除非模塊或包中的 __all__ 列表顯式地包含了它們 雙下劃線 (__) 1、名稱(具體為一個方法名)前雙下劃線(__)的用法并不是一種慣例,對解釋器來說它有特定的意義。Python 中的這種用法是為了避免與子類定義的名稱沖突。Python文檔指出,__spam 這種形式(至少兩個前導(dǎo)下劃線,最多一個后續(xù)下劃線)的任何標識符將會被 _classname__spam 這種形式原文取代,在這里 classname 是去掉前導(dǎo)下劃線的當前類名 2、名稱前后的雙下劃線表示 Python 中特殊的方法名。這只是一種慣例,對 Python 來說,這將確保不會與用戶自定義的名稱沖突。通常,程序員會重寫這些方法,并在里面實現(xiàn)所需要的功能,以便Python 調(diào)用
|