猜測下面這段程序的輸出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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() |
1、初探
正確的答案是:
1
2
|
A.__private() B.public() |
如果您已經猜對了,那么可以不看我這篇博文了。如果你沒有猜對或者心里有所疑問,那我的這篇博文正是為您所準備的。
一切由為什么會輸出“A.__private()”
開始。但要講清楚為什么,我們就有必要了解一下Python
的命名機制。
據 Python manual
,變量名(標識符)是Python的一種原子元素。當變量名被綁定到一個對象的時候,變量名就指代這個對象,就像人類社會一樣,不是嗎?當變量名出現在代碼塊中,那它就是本地變量;當變量名出現在模塊中,它就是全局變量。模塊相信大家都有很好的理解,但代碼塊可能讓人費解些。在這里解釋一下:
代碼塊就是可作為可執行單元的一段Python程序文本;模塊、函數體和類定義都是代碼塊。不僅如此,每一個交互腳本命令也是一個代碼塊;一個腳本文件也是一個代碼塊;一個命令行腳本也是一個代碼塊。
接下來談談變量的可見性,我們引入一個范圍的概念。范圍就是變量名在代碼塊的可見性。如果一個代碼塊里定義本地變量,那范圍就包括這個代碼塊。如果變量定義在一個功能代碼塊里,那范圍就擴展到這個功能塊里的任一代碼塊,除非其中定義了同名的另一變量。但定義在類中的變量的范圍被限定在類代碼塊,而不會擴展到方法代碼塊中。
2、迷蹤
據上節的理論,我們可以把代碼分為三個代碼塊:類A的定義、類B的定義和變量b的定義。根據類定義,我們知道代碼給類A定義了三個成員變量(Python
的函數也是對象,所以成員方法稱為成員變量也行得通。);類B定義了兩個成員變量。這可以通過以下代碼驗證:
1
2
3
4
5
6
7
8
9
|
>>> 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
的私有變量軋壓了。
3、探究
懂Python
的朋友都知道Python
把以兩個或以上下劃線字符開頭且沒有以兩個或以上下劃線結尾的變量當作私有變量。私有變量會在代碼生成之前被轉換為長格式(變為公有)。轉換機制是這樣的:在變量前端插入類名,再在前端加入一個下劃線字符。這就是所謂的私有變量軋壓(Private name mangling
)。如類A里的__private
標識符將被轉換為_A__private
,這就是上一節出現_A__private
和__private
消失的原因了。
再講兩點題外話:
-
一是因為軋壓會使標識符變長,當超過
255
的時候,Python
會切斷,要注意因此引起的命名沖突。 -
二是當類名全部以下劃線命名的時候,
Python
就不再執行軋壓。如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
>>> 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()
”吧!
4、真相
相信現在聰明的讀者已經猜到答案了吧?如果你還沒有想到,我給你個提示:真相跟C語言里的宏預處理差不多。
因為類A定義了一個私有成員函數(變量),所以在代碼生成之前先執行私有變量軋壓(注意到上一節標紅的那行字沒有?)。軋壓之后,類A的代碼就變成這樣了:
1
2
3
4
5
6
7
8
|
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()”
了。
下面的兩段代碼可以增加說服力,增進理解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
>>> 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
命名機制內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://zhuanlan.zhihu.com/p/65386589