與面試官談笑風生 | Python物件導向之訪問控制

simpleapples發表於2019-03-04

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是例項屬性,barbob都是公有的屬性,外部可以訪問,分別print類中的bar和例項中的bob,輸出了對應的值。

受保護(Protected)

在Python中定義一個受保護的屬性,只需要在其名字前加一個下劃線_,我們將Foo方法中的bobbar改為_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_barprint_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中函式是一等公民,所謂一等公民,就是函式可以像變數一樣使用,所以函式的訪問控制和屬性一樣,一樣應用上面的規則。

關注Python私房菜

只發優質Python文章的公眾號

相關文章