Python默認的成員方法和成員屬性都是公開的,沒有類似Java的public,private,protected等關鍵詞來修飾。 在python中定義私有變量只需要在變量名或函數名前加上 "__"兩個下划線,那么這個函數或變量就變成私有(方法也是一樣,方法名前面加了2個下划線的話表示該方法是私有的,否則為公有的)。
1. 類的變量類型
xx: 函數外為公有變量,函數內為局部變量
_x: 單前置下划線,protected類型的變量,不能用於’from module import *,即保護類型只能允許其本身與子類進行訪問。並未完全私有,只是表明不希望用戶直接訪問屬性,實際上,實例._變量,可以被訪問。
_xx:雙前置下划線,private類型的私有變量,無法在外部直接訪問(名字重寫所以訪問不到),但可以在類內部使用私有方法。本質是因為 python 編譯器做了一次名稱變換,
xx: 雙前后下划線,用戶名字空間的魔法對象或屬性。例如:__init__、__new__ , __ 不要自己發明這樣的名字
xx: 單后置下划線,用於避免與Python關鍵詞的沖突
2. 引入私有化場景
屬性賦值方法:在綁定屬性時,如果我們直接把屬性暴露出去,會出現屬性隨便更改的情況。
police = Role('Alex', 'police', 'AK47') # 實例化一個游戲人物
police.life_value = 0 # 生命值置為0
上面的情況很明顯不符合實際,我們實例化出來的游戲對象還沒開始,直接通過外部方法把生命值置為0,接下來game over!很明顯是作弊,因此我們引入私有化的概念,無論是屬性還是方法,在定義前加雙下划線__,就可使得屬性或方法只能在類內部通過某些特定的場景調用。
在下面的代碼中,訪問police.__life_value會提示"AttributeError: 'Role' object has no attribute '__life_value"。
class Role(object): def __init__(self, name, role, weapon, life_value=100, money=15000): self.name = name self._role = role # 這是一個protected類型的屬性
self.weapon = weapon self.__life_value = life_value # 這是一個私有類型的屬性
self.money = money def __shot(self): # 開了槍后要減子彈數
print("shooting...") def got_shot(self): # 中槍后要減血
self.__life_value -= 10
print(f"ah...,I got shot...,剩余生命值為{self.__life_value}") def buy_gun(self, gun_name, gun_money): # 檢查錢夠不夠,買了槍后要扣錢
if self.money > gun_money: self.money -= gun_money print("just bought %s" % gun_name) else: print("余額不足,無法支付買槍費用") police = Role('Alex', 'police', 'AK47') # 生成一個角色
print(police.__life_value)
無法檢查參數范圍:設置的屬性不符合實際。直接把屬性暴露出去,雖然寫起來很簡單,但是,沒辦法檢查參數,導致可以把成績隨便改。
zhangsan = Student("DY","100") zhangsan.score = 99999
實際開發中為了程序的安全,關於類的屬性都會封裝起來,Python中為了更好的保存屬性安全,不能隨意修改。一般屬性的處理方式為:
①將屬性定義為私有屬性。
②提供一個setter或getter方法
為了限制score的范圍,可以通過一個set_score()方法來設置成績,再通過一個get_score()來獲取成績,這樣,在set_score()方法里,就可以檢查參數:
class Student(object): def set_score(self, value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value def get_score(self): return self._score if __name__ == '__main__': s = Student()
私有化的使用場景:
- 屬性不允許在外部做更改
- 方法在某種特定場景下才可以執行
- 檢查參數范圍:外部設置超出范圍時校驗
3. 私有化封裝底層實現原理
__shot方法加上雙下划線后為什么對象外部訪問提示“AttributeError: 'Role' object has no attribute '__shot'”。
事實上,對於以雙下划線開頭命名的類屬性或類方法,Python 在底層實現時,將它們的名稱都偷偷改成了 "_類名__屬性(方法)名" 的格式。
所以__shot重寫為:_Role__shot。也因此訪問原來的方法名會提示類沒有__shot屬性,人家已經改名字了,當然訪問不到了。
# 使用 dir函數看看實例對象police里面有哪些內容,如下圖所示:
['_Role__life_value', '_Role__shot', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_role', 'buy_gun', 'got_shot', 'money', 'name', 'weapon'] #或可使用第二種方法
import inspect # 使用inspect檢查現場對象
methods = inspect.getmembers(police, predicate=inspect.ismethod) print(methods) 執行結果: [('_Role__shot', <bound method Role.__shot of <__main__.Role object at 0x000001D3F6FFCE48>>), ('__init__', <bound method Role.__init__ of <__main__.Role object at 0x000001D3F6FFCE48>>), ('buy_gun', <bound method Role.buy_gun of <__main__.Role object at 0x000001D3F6FFCE48>>), ('got_shot', <bound method Role.got_shot of <__main__.Role object at 0x000001D3F6FFCE48>>)]
私有化屬性或方法有方式可以訪問嗎?
4. 訪問私有化屬性/方法
上面有說屬性前加雙下划線無法訪問的原因,是因為在Python編譯器重寫變量名/方法名,所以導致訪問不到。
在規范上,這種雙下划線的私有方法和私有屬性是不應該在外部訪問的,但是如果就是想強行訪問還是有方法的:直接訪問重寫名稱后的屬性名或方法名。
police._Role__shot() # 強行調用私有方法
print(police._Role__life_value) # 強行調用私有屬性
執行結果: shooting... 100