2012-07-24 74 views
7

Python類沒有public/private的概念,所以我們被告知不要觸及以下劃線開頭的東西,除非我們創建它。但是這並不需要我們直接或間接繼承的所有類的完整知識嗎?證人:Python是否需要繼承鏈中所有類的知識?

class Base(object): 
    def __init__(self): 
     super(Base, self).__init__() 
     self._foo = 0 

    def foo(self): 
     return self._foo + 1 

class Sub(Base): 
    def __init__(self): 
     super(Sub, self).__init__() 
     self._foo = None 

Sub().foo() 

預期地,當None + 1被評估TypeError上升。所以我必須知道_foo存在於基類中。爲了解決這個問題,__foo可以用來代替,它通過改變名稱來解決問題。這似乎是一種可以接受的解決方案,如果不是優雅的話。然而,如果Base繼承自一個名爲Sub的類(在單獨的包中)會發生什麼?現在__foo在我的Sub中重寫__foo在祖父母Sub中。

這意味着我必須知道整個繼承鏈,包括每個使用的所有「私有」對象。 Python是動態類型的這一事實使得這更加困難,因爲沒有聲明來搜索。然而,最糟糕的部分可能是Base現在可能繼承object的事實,但在未來的某個版本中,它將切換爲從Sub繼承。很顯然,如果我知道Sub是從繼承的,我可以重命名我的班級,然而令人討厭的是。但我看不到未來。

這不是真正的私人數據類型可以防止問題的情況嗎?在Python中,如果這些腳趾可能會在未來某個時刻出現,我是否可以確定我不會意外踩到某人的腳趾?

編輯:我顯然沒有說清楚主要問題。我熟悉名稱的改變以及單個和雙下劃線之間的區別。問題是:我該如何處理這樣的事實,即我可能會碰到類別,這些類別的存在我現在不知道?如果我的父類(它在一個我沒有寫的包中)碰巧開始從與我的類同名的類繼承,即使名稱修改也無濟於事。我認爲這是一個真正的私人成員可以解決的(角落)情況,但我認爲Python有問題嗎?

編輯:按照要求,下面是一個完整的例子:

文件parent.py

class Sub(object): 
    def __init__(self): 
     self.__foo = 12 
    def foo(self): 
     return self.__foo + 1 
class Base(Sub): 
    pass 

文件sub.py

import parent 
class Sub(parent.Base): 
    def __init__(self): 
     super(Sub, self).__init__() 
     self.__foo = None 
Sub().foo() 

祖父母的foo叫,但我使用__foo

顯然你不會自己寫這樣的代碼,但parent可以很容易地由第三方提供,其細節可以在任何時候改變。

+0

你可以發佈一個例子來演示這個地方其他同名的類不會完全影響你現有的類嗎..或者我還是不明白..nevermind ...我做了一個和你的權利我猜這是一個角落case ...只是儘量不要使用常見的私人var /類名稱我猜 – 2012-07-24 22:04:01

回答

2

這意味着我必須知道整個繼承鏈。 。 。

是的,你應該知道整個繼承鏈,或者你直接分類的對象的文檔應該告訴你你需要知道什麼。

子類化是一項高級功能,應小心處理。

文檔指定什麼應該在子類中被覆蓋的一個很好的例子是threading class

這類表示在單獨的控制線程中運行的活動。有兩種方法可以指定活動:將可調用對象傳遞給構造函數,或者通過覆蓋子類中的run()方法。在子類中不應該重寫其他方法(構造函數除外)。換句話說,只能覆蓋此類的__init__()run()方法。

7

使用private names(而不是保護的),開始以雙下劃線:

class Sub(Base): 
    def __init__(self): 
     super(Sub, self).__init__() 
     self.__foo = None 
     # ^^ 

不會Base_foo__foo衝突。這是因爲Python用一個下劃線和類名替換了雙下劃線;下面兩行是等價的:

class Sub(Base): 
    def x(self): 
     self.__foo = None # .. is the same as .. 
     self._Sub__foo = None 

(響應編輯:)的機會,兩個班的類層次結構,不僅具有相同的名稱,但它們都使用相同的屬性名兩者, (__)形式是非常小的,它可以在實踐中安全地被忽略(我爲一個迄今沒有聽說過一個單一的案例)。

然而,理論上你是正確的,爲了正式驗證程序的正確性,大多數人都知道整個繼承鏈。幸運的是,在任何情況下,形式驗證通常都需要一套固定的庫。

這是Zen of Python的精神,其包括

實用性節拍純度。

+0

但它會與假設的'Base .__ foo'發生衝突嗎? – inspectorG4dget 2012-07-24 21:41:48

+0

@ inspectorG4dget不是。 – phihag 2012-07-24 21:42:32

+0

我認爲這是值得在你的答案特別提到,因爲它更符合什麼OP似乎要求 – inspectorG4dget 2012-07-24 21:43:42

3
  1. 名稱重整包括類,所以你Base.__fooSub.__foo會有不同的名字。這是首先將名稱加密功能添加到Python的全部原因。一個是_Base__foo,另一個是_Sub__foo

  2. 許多人更喜歡使用組合(has-a)而不是繼承(is-a),因爲其中一些原因。

+0

是不是這個錯誤?我認爲只有發生雙重下劃線不能單獨發生... – 2012-07-24 21:46:32

+0

我提到名字mangling;我注意到的特殊情況是A-> B-> A(其中第一個A顯然是在一個單獨的包中)。現在,祖父類會像我的子類一樣破壞事物,消除名稱集合提供的優勢。 – Chris 2012-07-24 21:47:35

+0

@JoranBeasley:我使用雙下劃線... – 2012-07-24 23:21:24

0

如上所述,您可以使用名稱修飾。然而,如果你充分記錄你的代碼,你可以堅持一個下劃線(或者沒有!) - 你不應該有太多的私有變量,這證明是一個問題。只要說一個方法依賴於一個私有變量,並將該變量或方法的名稱添加到類文檔字符串中以提醒用戶。另外,如果你創建單元測試,你應該創建測試來檢查成員的不變量,因此這些應該能夠顯示出這樣的名稱衝突。

如果你真的想擁有「私有」變量,不管出於什麼原因名稱忙玲不能滿足你的需求,那麼您可以您的私人狀態到另一個對象:

class Foo(object): 

    class Stateholder(object): pass 

    def __init__(self): 
     self._state = Stateholder() 
     self.state.private = 1 
+0

這意味着,一旦你寫了一個類,你永遠不能修改它的工作方式,因爲任何從它繼承的人可能使用了你自己可能會用在你的名字修改?如果沒有真正的治療方法,單元測試可能是治療症狀的最佳方法。 – Chris 2012-07-24 22:15:35

+0

@Chris你想象中的問題並不存在於你想象的形式中。如果你引入一個新的公共名稱,也會發生同樣的事情。解決方案是配置管理,這是軟件開發的一個正常部分。在任何情況下,只有在創建庫時纔會出現此問題 - 如果它位於一個代碼庫中,只需向同事說明即可。 – Marcin 2012-07-24 22:26:52

+0

在公共/私人理念非常鬆散的語言中,情況確實如此。兩者之間具有強烈區別的語言將使得擁有「穩定的API,可變內部」方法變得容易;我可以獲得一個不變的API,而不僅僅是不變的內部。我認爲缺乏這樣的區分是一個問題,但我接受其他人不這樣做。 – Chris 2012-07-24 22:44:08

0

忙玲發生雙下劃線。單下劃線更多的是「請不要」。你不需要知道所有父類的所有細節(注意深度繼承通常是最好的避免),因爲你仍然可以使用dir()和help()以及任何其他形式的自省用。

2

您多久修改一次繼承鏈中的基類,以引入繼承鏈中的繼承?

不那麼輕浮,是的,你必須知道你正在使用的代碼。畢竟,你必須知道所使用的公共名稱。 Python是python,發現祖先類使用的公有名稱與發現私有名稱幾乎一樣努力。

在多年的Python編程中,我從未發現這在實踐中會成爲一個問題。當你命名實例變量時,你應該有一個很好的主意,是否(a)一個名稱是足夠通用的,以至於它可能用在其他上下文中;(b)你正在編寫的類可能涉及繼承層次結構與其他未知類。在這種情況下,你更仔細地考慮一下你使用的名字; self.value不是一個屬性名稱的好主意,也不是類似Adaptor這樣的好名字。

相比之下,我遇到過多使用雙下劃線名稱的困難。 Python是Python,即使是「私有」名稱也傾向於通過在類之外定義的代碼來訪問。您可能認爲讓外部函數訪問「私有」屬性總是不好的做法,但getattrhasattr之類的事情呢?它們的調用可以在類的自己的代碼中,所以類仍然控制對私有屬性的所有訪問,但是如果沒有您手動執行名稱修改,它們仍然不起作用。如果Python實際上實施了私有變量,那麼您根本無法使用像它們那樣的函數。現在,當我寫一些非常通用的裝飾器,元類或混合類(需要向其應用的(未知)類的實例中添加「祕密屬性」)時,我傾向於保留雙下劃線名稱。

當然還有標準的動態語言參數:事實是,您必須徹底測試您的代碼,才能在聲明「我的軟件能夠正常工作」時有足夠的理由。這樣的測試將不太可能錯過由意外衝突引起的錯誤。如果你沒有進行這種測試,那麼很多更多的未被發現的錯誤將通過其他方式引入,而不是意外的名稱衝突。

總而言之,實踐中缺乏私有變量在慣用Python代碼中並不算什麼大事,而增加真正的私有變量會導致其他方式更頻繁的問題。