早安,有段時間沒見了,而這一篇文只是確認生存用的,所以並不會太長。
Name Mangling
在 Python 中撰寫 class 時,只要在屬性名字的前面加上兩個底線(如 __spam
),這個屬性的名字最後就會被重組成 _Cls__spam
這樣的形式,這項技術稱作 Name Mangling,提供了類似其他語言的私用屬性。
但要注意的是,就如前面所提到的,類別外部的實例依舊可用重組後的名稱來存取該屬性,所以這並不能做為防止有心人士修改重要資料的方法;事實上,Name Mangling 的存在是為了避免外界對特定屬性的意外存取,或不小心覆寫了父類別的屬性、方法。
class MyClass:
def __init__(self):
self.__bar = 1
m = MyClass()
print(m._MyClass__bar) # 1
但其實,Name Mangling 有個鮮為人知的陰謀...
哪些東西會被 Mangled?
「這話是什麼意思?不就是兩個底線開頭的屬性嗎?」看到標題後,有些人應該會這麼問。的確,像上例的 __bar
會是重組的目標,但其實,還有一些東西也是。
根據 Python 官方文件表示:
When an identifier that textually occurs in a class definition begins with two or more underscore characters and does not end in two or more underscores, it is considered a private name of that class. ... This transformation is independent of the syntactical context in which the identifier is used.
前面的粗體字說明了被重組的名稱必須以 2 個或以上的底線所開頭,且結尾最多只有一個底線;而後面的粗體則是說,這個 mangling 轉換並不侷限於特定的語境(syntactical context),亦即不只有 self.__spam
這裡的 __spam
會被重組,任何出現在 class 內文的變數,只要名稱形式符合,都會被重組。
舉例來說:
class MyClass:
def hello(self):
__foo = 101 # this will be mangled
return _MyClass__foo
def __print(self):
print("this will be mangled, too")
__cls_attr = 'another identifier that will be mangled'
m = MyClass()
print(m.hello()) # 101
m._MyClass__print() # this will be mangled, too
print(MyClass._MyClass__cls_attr) # another identifier that will be mangled
上例中列出了會被名稱重組的三種可能情況,其中比較有趣的是 hello
方法中的 __foo
,在原始方法內文中,我們可以同時使用兩種名稱來存取他,分別是 __foo
和 mangling 後的 _MyClass__foo
。
那些不會被重組的
前面的引文也有提到,被重組的名稱必須是個 identifier,因此單純的字串內出現 __foo
這樣的文字並不會被替換,不過這會有什麼問題嗎?
有時候我們會使用 __dict__
或其他函式來存取實例的屬性,而如果目標屬性會被 mangling 所影響,那麼我們對 __dict__
所使用的鍵或傳入其他函式的引數就必須是已經重組過的名字:
class MyClass:
def __init__(self):
self.__data = 101
def get_data(self):
return self.__dict__['_MyClass__data']
m = MyClass()
m.get_data() # 101
雖然這個例子不太好,但能夠展示我想表達的就行了。
這次講的內容其實並不容易造成什麼問題,只是偶然在 werkzeug 的 source code 裡發現一些神奇的地方,再經過自己實驗後觀察到的現象。
最後,感謝看到這裡的你,如果發現這篇文章有什麼瑕疵或想回饋的,都可以在下面留言。
參考資料:
後記:
不知道有沒有人發現前兩篇歌詞翻譯的作者欄位裡並不是我的名字XD
話說上一篇(Answer)的搜尋排名跟次數大幅上升中,目前已經擠到搜尋結果的前三了OAO,有點受寵若驚,我是不是該轉職了(誤)