什么是偽私有屬性?
在Python中,沒有類似 private 之類的關鍵字來聲明私有方法或屬性。
Python中要聲明私有屬性,需要在屬性前加上雙下划線(但是結尾處不能有雙下划線),如:self.__a。然而這樣的什么方式並不是真正私有,而是“偽私有”。
Python的偽私有屬性,實際是通過變量名壓縮(mangling)來實現變量名局部化。變量名壓縮的規則:在初始的變量名頭部加上一個下划線,再加上類的名稱,最后是初始變量名的名稱。
執行以下代碼來驗證:
class A(object): def __func(self):pass if __name__ == '__main__': print(A.__dict__)
運行結果:
{'__weakref__': <attribute '__weakref__' of 'A' objects>, '__module__': '__main__', '__doc__': None, '_A__func': <function A.__func at 0x10cfa037 8>, '__dict__': <attribute '__dict__' of 'A' objects>}
我們通過類的__dict__屬性,將class A的所有屬性打印出來,從打印的結果可以發現:原先定義的偽私有屬性(方法):__func 在__dict__中並不存在,取而代之的是_A_func這個方法,方法__func的變量名被壓縮。
如此在外部調用class A的__func方法時,會提示無法找到。修改代碼進行測試:
class A(object): def __func(self):pass if __name__ == '__main__': a = A() a.__func()
運行后出現異常,提示A沒有屬性__func,從而實現類似私有屬性的功能。
AttributeError: 'A' object has no attribute '__func'
之所以說它是“偽私有”,是因為在了解偽私有變量的變量名壓縮規則后,可以根據壓縮規則進行調用。
再次修改代碼進行驗證:
class A(object): def __func(self):print('Hello Python') if __name__ == '__main__': a = A() a._A__func()
運行結果正常, 成功打印“Hello Python”字符串。
Hello Python
所以,Python的類並不存在正在的私有屬性,通過雙下划線實現的偽私有屬性,本質上是對變量名進行壓縮,使之無法直接在外部調用。
為什么要使用偽私有屬性
使用偽私有屬性是為了避免在類樹中,多個類賦值相同的屬性引發沖突問題。
假設有兩個類,C1 和 C2,他們都有相同的屬性X。
class C1(): def meth1(self): self.x = 'Hello World' def meth2(self): print(self.x)
c1 = C1() c1.meth1()
c1.meth2()
class C2(): def meth3(self): self.x = 'Hello Python' def meth4(self): print(self.x)
c2 = C2() c2.meth3()
c2.meth4()
類C1和C2在單獨調用時,輸出結果沒有問題,符合預期:調用meth2方法時,打印meth1的賦值結果;調用meth4方法時,打印meth3的賦值結果。
此時增加一個新的類C3,繼承自C1、C2(多重繼承):
class C1(): def meth1(self): self.x = 'Hello World' def meth2(self): print(self.x) class C2(): def meth3(self): self.x = 'Hello Python' def meth4(self): print(self.x) class C3(C1, C2): pass c3 = C3() c3.meth1() c3.meth3() c3.meth2() c3.meth4()
從運行結果可以看出,每次 print(self.x)的內容,取決於 self.x 最后一次賦值的內容。
Hello Python
Hello Python
在示例代碼中,先調用 c3.meth1() 進行賦值,self.x的值為“Hello World”,再調用 c3.meth3() 進行賦值時,self.x的值被覆蓋,目前的值為“Hello Python”。
后續再調用c3.meth2()打印self.x的值時,實際上打印的是最后一次賦值結果,這在有些情況下跟類的設計初衷是相違背的:在C1中,meth2希望打印的是在meth1中賦值的內容:“Hello World”。
在使用偽私有屬性后可以解決變量名self.x相互覆蓋的問題(因為self.__x 被壓縮成了 self._C1__x 和 self._C2__x,變量名不同,不會互相覆蓋):
class C1(): def meth1(self): self.__x = 'Hello World' def meth2(self): print(self.__x) class C2(): def meth3(self): self.__x = 'Hello Python' def meth4(self): print(self.__x) class C3(C1, C2): pass c3 = C3() c3.meth1() c3.meth3() c3.meth2() c3.meth4()
運行結果符合C1的設計初衷:調用meth2時應該打印出meth1的賦值結果:
Hello World
Hello Python