引子我熱情地邀請大家猜測下面這段程式的輸出: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()Python類中雙底線的私人成員(私人函數,私人變數)
一、Python中預設的成員函數、成員變數都是公開的(public),而且python中沒有類似public、private等關鍵詞來修飾成員函數,成員變數。
在python中定義私人變數只需要在變數名或函數名前加上 "__" (兩個底線),那麼這個函數或變數就會成為私人的了。
在內部,python使用一種 name mangling 技術,將__membername替換成 _classname__membername,所以你在外部使用原來的私人成員的名字時,會提示找不到。
命名混淆意在給出一個在類中定義"私人"執行個體變數和方法的簡單途徑,避免衍生類別的執行個體變數定義產生問題,或者與外界代碼中的變數搞混。
要注意的是混淆規則主要目的在於避免意外錯誤,被認作為私人的變數仍然有可能被訪問或修改(使用_classname__membername),在特定的場合它也是有用的,比如調試的時候。
【範例程式碼】
# -*- coding: utf-8 -*-
class Test(object):
def __init__(self):
super(Test, self).__init__()
self.__member = 99
def __method(self):
print "Test.__method"
if __name__ == '__main__':
t = Test()
print dir(t)
#t.__method() # AttributeError: 'Test' object has no attribute '__method'
#print t.__member # AttributeError: 'Test' object has no attribute '__member'
t._Test__method() # 使用Python更改之後的函數名依然可以訪問到"私人"函數
print t._Test__member # 使用Python更改之後的變數名依然可以訪問到"私人"變數
'''
['_Test__member', '_Test__method', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '__weakref__']
Test.__method
99
'''
可以看到在dir裡面多的不是__member和__method,而是_Test__member和_Test__method。並且可以通過_Test__member和_Test__method訪問原來類裡面的成員變數和成員函數。
二、單底線開頭的成員變數和成員函數,其實就是public的,不等同於protected屬性。
【範例程式碼】
# -*- coding: utf-8 -*-
class Test(object):
def __init__(self):
super(Test, self).__init__()
self.__member = 88
self._member = 99
def __method(self):
print "Test.__method"
def _method(self):
print "Test._method"
class TestDerived(Test):
def __init__(self):
super(TestDerived, self).__init__()
def test(self):
#print self.__member # AttributeError: 'TestDerived' object has no attribute '_TestDerived__member'
#self.__method() # AttributeError: 'TestDerived' object has no attribute '_TestDerived__method'
print self._member
self._method()
if __name__ == '__main__':
td = TestDerived()
print dir(td)
td.test()
#print td.__member # AttributeError: 'TestDerived' object has no attribute '__member'
#td.__method() # AttributeError: 'TestDerived' object has no attribute '__method'
print td._member # 外部可以直接存取單底線開頭的成員變數
td._method() # 外部可以直接存取單底線開頭的成員函數
'''
['_Test__member', '_Test__method', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_member','_method', 'test']
99
Test._method
99
Test._method
'''
無論是單底線還是雙底線開頭的成員,都是希望外部程式開發人員不要直接使用這些成員變數和這些成員函數,只是雙底線從文法上能夠更直接的避免錯誤的使用,但是如果按照 _類名__成員名 則依然可以訪問到。單底線的在動態調試時可能會方便一些,只要項目組的人都遵守底線開頭的成員不直接使用,那使用單底線或許會更好。
所有的 Python 模組都是對象並且有幾個有用的屬性。 您可以使用這些屬性方便地測試您所書寫的模組。 下面是一個使用 if __name__ 的技巧。
if __name__ == "__main__":
在繼續學習新東西之前, 有幾點重要的觀察結果。 首先, if 運算式無需使用圓括弧括起來。 其次, if 語句以冒號結束, 隨後跟隨的是 縮排代碼。
與 C 一樣, Python 使用 == 做比較, 使用 = 做賦值。 與 C 不一樣, Python 不支援行內賦值, 所以不會出現想要進行比較卻意外地出現賦值的情況。
那麼為什麼說這個特殊的 if 語句是一個技巧呢?模組是對象, 並且所有的模組都有一個內建屬性 __name__。一個模組的 __name__ 的值要看您如何應用模組。如果 import 模組, 那麼 __name__的值通常為模組的檔案名稱, 不帶路徑或者副檔名。但是您也可以像一個標準的程式一樣直接運行模組, 在這種情況下 __name__的值將是一個特別的預設值, __main__。
>>> import odbchelper
>>> odbchelper.__name__
'odbchelper'
一旦瞭解到這一點, 您可以在模組內部為您的模組設計一個測試套件, 在其中加入這個 if 語句。當您直接運行模組, __name__ 的值是 __main__, 所以測試套件執行。當您匯入模組, __name__的值就是別的東西了, 所以測試套件被忽略。這樣使得在將新的模組整合到一個大程式之前開發和調試容易多了.
在 MacPython 上, 需要一個額外的步聚來使得 if __name__ 技巧有效。 點擊視窗右上方的黑色三角, 彈出模組的屬性菜單, 確認 Run as __main__ 被選中。