理解Python命名機制本文最初發表於戀花蝶的部落格(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()如果您已經猜對了,那麼可以不看我這篇博文了。如果你沒有猜對或者心裡有所疑問,那我的這篇博文正是為您所準備的。一切由為什麼會輸出“A.__private()”開始。但要講清楚為什麼,我們就有必要瞭解一下Python的命名機制。據 Python manual,變數名(標識符)是Python的一種原子項目。當變數名被綁定到一個對象的時候,變數名就指代這個對象,就像人類社會一樣,不是嗎?當變數名出現在代碼塊中,那它就是本地變數;當變數名出現在模組中,它就是全域變數。模組相信大家都有很好的理解,但代碼塊可能讓人費解些。在這裡解釋一下:代碼塊就是可作為可執行單元的一段Python程式文本;模組、函數體和類定義都是代碼塊。不僅如此,每一個互動指令碼命令也是一個代碼塊;一個指令檔也是一個代碼塊;一個命令列指令碼也是一個代碼塊。接下來談談變數的可見度,我們引入一個範圍的概念。範圍就是變數名在代碼塊的可見度。如果一個代碼塊裡定義本地變數,那範圍就包括這個代碼塊。如果變數定義在一個功能代碼塊裡,那範圍就擴充到這個功能塊裡的任一代碼塊,除非其中定義了同名的另一變數。但定義在類中的變數的範圍被限定在類代碼塊,而不會擴充到方法代碼塊中。
迷蹤據上節的理論,我們可以把代碼分為三個代碼塊:類A的定義、類B的定義和變數b的定義。根據類定義,我們知道代碼給類A定義了三個成員變數(Python的函數也是對象,所以成員方法稱為成員變數也行得通。);類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消失了!這就要談談Python的私人變數軋壓了。
探究懂Python的朋友都知道Python把以兩個或以上底線開頭且沒有以兩個或以上底線結尾的變數當作私人變數。私人變數會在代碼產生之前被轉換為長格式(變為公有)。轉換機制是這樣的:在變數前端插入類名,再在前端加入一個底線。這就是所謂的私人變數軋壓(Private name mangling)。如類A裡的__private標識符將被轉換為_A__private,這就是上一節出現_A__private和__private消失的原因了。再講兩點題外話:一是因為軋壓會使標識符變長,當超過255的時候,Python會切斷,要注意因此引起的命名衝突。二是當類名全部以底線命名的時候,Python就不再執行軋壓。如:>>> 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() # 可以外部調用____.__method()現在我們回過頭來看看為什麼會輸出“A.__private()”吧!
真相相信現在聰明的讀者已經猜到答案了吧?如果你還沒有想到,我給你個提示:真相跟C語言裡的宏預先處理差不多。因為類A定義了一個私人成員函數(變數),所以在代碼產生之前先執行私人變數軋壓(注意到上一節標紅的那行字沒有?)。軋壓之後,類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__方法,所以調用的仍然是A.__init__,即執行了self._A__private(),自然輸出“A.__private()”了。下面的兩段代碼可以增加說服力,增進理解:>>> class C(A): def __init__(self): # 重寫__init__,不再調用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() # 調用一個沒有定義的函數,Python會把它給我的 ^_^~ self.public() def __private(self): print 'A.__private()' def public(self): print 'A.public()'>>>a = A()A.__private()A.public()