Python從設計之初就是一門物件導向的語言,物件導向思想的第一個要素就是封裝。所謂封裝,通俗的講就是類中的屬性和方法,分為公有和私有,公有可以被外界訪問,私有不能被外界訪問,這就是封裝中最關鍵的概念——訪問控制。
訪問控制有三種級別:私有、受保護、公有
私有(Private):只有類自身可以訪問 受保護(Protected):只有類自身和子類可以訪問 公有(Public):任何類都可以訪問
由於Python不像Java,有訪問控制符(private / public / protected),所以Python的訪問控制也是容易被應聘者忽視和搞錯的。
公有(Public)
在Python的類中,預設情況下定義的屬性都是公有的。
class Foo(object):
bar = 123
def __init__(self, bob):
self.bob = bob
print(Foo.bar) # 123
foo = Foo(456)
print(foo.bob) # 456
複製程式碼
上面類Foo
中的bar
屬性就是類屬性,__init__
方法中定義的bob是例項屬性,bar
和bob
都是公有的屬性,外部可以訪問,分別print類中的bar
和例項中的bob
,輸出了對應的值。
受保護(Protected)
在Python中定義一個受保護的屬性,只需要在其名字前加一個下劃線_
,我們將Foo方法中的bob
和bar
改為_bob
和_bar
,他們就變成了受保護的屬性了,程式碼如下:
class Foo(object):
_bar = 123
def __init__(self, bob):
self._bob = bob
class Son(Foo):
def print_bob(self):
print(self._bob)
@classmethod
def print_bar(cls):
print(cls._bar)
Son.print_bar() # 123
son = Son(456)
son.print_bob() # 456
複製程式碼
定義一個類Son
繼承自Foo
,由於受保護的物件只能在類的內部和子類中被訪問,不能直接呼叫print(Son._bar)
或print(son._bob)
來輸出這兩個屬性的值,所以定義了print_bar
和print_bob
方法,實現在子類中輸出,這段程式碼也正常的輸出了_bar
和_bob
的值。
接下來,試著反向驗證一下,在類的外部,能不能訪問其屬性,將上面程式碼的輸出部分修改如下:
print(Son._bar) # 123
son = Son(456)
print(son._bob) # 456
複製程式碼
(假裝)驚訝的發現,竟然沒有報錯,也輸出了正確的值。
Python中用加下劃線來定義受保護變數,是一種約定的規範,而不是語言層面真的實現了訪問控制,所以,我們定義的保護變數,依然可以在外部被訪問到(這是個feature,不是bug)。
私有(private)
Python定義私有屬性,需要在屬性名前加兩個下劃線__
,把上面的程式碼修改一下,執行一下會發現下面的程式碼中的任何一個print都會報錯的。
class Foo(object):
__bar = 123
def __init__(self, bob):
self.__bob = bob
class Son(Foo):
def print_bob(self):
print(self.__bob) # Error
@classmethod
def print_bar(cls):
print(cls.__bar) # Error
print(Son.__bar) # Error
son = Son(456)
print(son._bob) # Error
複製程式碼
深入一下——私有屬性真的就訪問不到了嗎?
要了解私有屬性是否真的訪問不到,需要從Python是如何實現私有屬性入手。CPython中,會把雙下劃線的屬性變為_ClassName__PropertyName
的形式,用程式碼演示一下:
class Foo(object):
__bar = 123
print(Foo._Foo__bar) # 123
複製程式碼
執行一下可以知道,正常輸出了__bar
的值,但是不推薦這樣去訪問私有屬性,因為不同的Python直譯器對於私有屬性的處理不一樣。
特例
使用雙下劃線定義私有屬性,有一種特殊情況,當屬性後也有兩個下劃線的時候,這個屬性會被Python直譯器當做魔術方法,從而不做私有處理。
class Foo(object):
__bar__ = 123
print(Foo.__bar__) # 123
複製程式碼
上面程式碼輸出了123,證明Python直譯器並沒有把__bar__
當做私有屬性。當定義私有屬性時,需要注意名字最後最多隻能有一個下劃線。
另一個特例
假如定義的屬性名就叫__
呢?不妨直接試一下:
class Foo(object):
__ = 123
print(Foo.__) # 123
複製程式碼
可以發現名字叫__
的屬性也不會被認為是私有屬性,名字是多個下劃線的屬性也不是私有屬性(比如_______
)。
函式的訪問控制
前面主要介紹了屬性的訪問控制,在Python中函式是一等公民,所謂一等公民,就是函式可以像變數一樣使用,所以函式的訪問控制和屬性一樣,一樣應用上面的規則。