標籤:同名 調用 模組 學習 new nbsp 引入 根據 疑問
add by zhj:今天在學習SimpleHTTPServer的原始碼時,看到了Python標準庫SocketServer模組中有個BaseServer類,該類的__init__方法定義如下
def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address self.RequestHandlerClass = RequestHandlerClass self.__is_shut_down = threading.Event() self.__shutdown_request = False
看到它的注釋寫著,該類不能override。在初始化時,它定義了__is_shut_down和__shutdown_request兩個變數,我之前對__xx這樣的變數的含義不理解,
現在聯想到會不會是這種變數不能被子類override呢?然後查到了下面這篇文章,果然如此。Python已經從解譯器層面考慮到了這種私人變數,保證了這種
變數不會被子類的同名變數覆蓋,很牛逼,這樣的話,其實子類就完全沒有必要再定義同名的__xx變數了,因為即使定義了,也不能覆蓋父類中的同名變數,
而且這樣寫易讀性很差,很容易讓人搞混。Python這個trick做得還是挺不錯的。其實,之前對__xx變數的含義也有瞭解,但當時只思考它的工作原理,並不
理解它這樣做到底是為了什麼,回頭想想:當時自己本末倒置了,如果Crowdsourced Security Testing道這樣做的目的,再理解原理就更容易些。
原文:未找到
引子我熱情地邀請大家猜測下面這段程式的輸出: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的雙底線命名(轉)