2011-09-30 14 views
2

這個腳本:的Python直觀的成員變量的行爲

class testa(): 
    a = [] 

class testb(): 
    def __init__(self): 
     self.a = [] 

ta1 = testa(); ta1.a.append(1); ta2 = testa(); ta2.a.append(2) 
tb1 = testb(); tb1.a.append(1); tb2 = testb(); tb2.a.append(2) 

print ta1.a, ta2.a, tb1.a, tb2.a 

產生這樣的輸出:

[1, 2] [1, 2] [1] [2] 

,但我預計

[1] [2] [1] [2] 

爲什麼是我錯了嗎? testa和testb的定義對我來說似乎相同,那麼爲什麼行爲會如此劇烈地變化?

編輯:這似乎不直觀,因爲它不同於其他類型如int和str的行爲。由於某些原因,在init未初始化時,列表被創建爲類變量,但是int和strs被創建爲對象變量,無論如何。

+1

您在字符串,整數和列表之間所看到的差異是列表是可變的,但字符串和整數是不可變的。您不能更改字符串的值。您可以綁定到不同的字符串。 –

+3

字符串和整數也被創建爲類變量。只是你不能在一個int上調用'append',你必須用一個賦值重新綁定它。這會創建一個** new ** int對象作爲實例變量(除非您在類中重新綁定它),並將類變量隱藏(對於特定實例),但保持不變。 – Ben

+0

好像我們最近已經得到了這個問題的幾十個變種...... –

回答

7

testa變量a類變量並在所有實例之間共享。 ta1.ata2.a指的是相同的列表。

testb變量a對象變量。每個實例都有自己的價值。

有關更多詳細信息,請參見Class and Object Variables

3

它有助於記住Python中的class語句與其他語句比C++語言更接近任何其他語句。

在Python中,class語句包含一堆要執行的語句,就像defifwhile。不同之處在於譯員對這個區塊做了什麼。在流程控制塊語句(如ifwhile)的情況下,解釋器根據流控制語句的含義指定執行塊。在def中,解釋程序保存塊並在調用函數對象時執行它。

class塊的情況下,Python的執行塊立即,在一個新的範圍,然後使用無論是在該範圍內執行左完成作爲類的內容之後。

因此,對於這個:

class testa(): 
    a = [] 

Python的執行塊a = []。然後在最後,範圍包含綁定到空列表對象的a。所以這就是你的。沒有任何特定的類的實例,那就是類本身。

它從object繼承了一個無所作爲的構造函數,因此當您用ta1 = testa()實例化類時,會得到一個空實例。然後,當您詢問ta1.a時,Python找不到名爲a的實例變量(因爲ta1根本沒有實例變量),所以它在testa類中查找a。這當然會發現;但對於testa的每個實例它都會找到相同的結果,因此您可以看到您觀察到的行爲。

在另一方面這樣的:

class testb(): 
    def __init__(self): 
     self.a = [] 

是完全不同的。這裏一旦類塊被執行,類作用域的內容又是一個單一的名字,但這次它綁定到一個函數。這成爲課堂的內容。

現在,當你實例這個testb(),Python中發現__init__在類並調用新的實例功能。該函數的執行會在新實例中創建一個變量a實例變量。因此,testb()的每個實例都有自己的變量,並且您會看到所觀察到的行爲。

帶回家的消息:在Python class只是一組包含在該類的實例,與傳統的面向對象十歲上下的語言,如C++和Java的東西聲明。這是實際執行的代碼,用於定義類的內容。這可以非常方便:您可以在您的類體內使用if語句,函數執行的結果以及任何其他上下文中使用的任何內容來決定在類中定義什麼。

注意:我騙了簡單甚至早實例testa會有一些實例變量,因爲有一些默認情況下所有實例自動創建的,但你沒有看到他們在一天多。一天的Python)。

+0

值得強調的是,類**是Python中的**對象。反射是內置的,不需要圖書館。 –

0

testa和testb的定義看起來與我相當,那麼爲什麼行爲應該如此劇烈地變化呢?

Python的行爲是完全合乎邏輯的 - 更何況比Java或C++或C#的,在聲明中class範圍屬於類一切。

在Python中,類在邏輯上是對象的模板,但它實際上並不是物理上的一個。您可以指定「實例具有這組成員」的最接近的方法是從object繼承,將它們列在類'__slots__中並將它們初始化爲__init__。但是,Python不會強制您設置__init__中的所有值,您沒有設置的值不會得到任何默認值(嘗試訪問它們會導致AttributeError),您甚至可以在以後顯式刪除實例中的屬性(通過del)。所有__init__所做的都是在每個新實例上運行(這是設置屬性初始值的方便和慣用的方式)。 __slots__確實是阻止加入其他屬性。

這看起來很不直觀,因爲它與int和str等其他類型的行爲不同。出於某種原因,在init中未初始化時,列表會創建爲類變量,但無論如何,int和strs都會創建爲對象變量。

事實並非如此。

如果您將intstr放在那裏,它仍然是該類的成員。如果您可以以某種方式修改intstr,那麼這些更改將反映在每個對象中,就像它們在列表中一樣,因爲查找對象中的屬性會在類中找到它。 (粗略地說,屬性訪問首先檢查對象,然後是類。)

但是,你實際上不能這樣做。你認爲什麼是等效測試根本就是不相同的,因爲你用intstr進行的測試會導致對對象屬性的分配,這就隱藏了class屬性。 ta1.a.append調用列表的方法,這會導致列表的內部發生更改。對Python的字符串和整數來說這是不可能的。即使你寫了類似ta1.n += 1的東西,這也會轉化爲ta.n = ta.n + 1,其中查找屬性(並在類中找到),計算出的總和,然後分配回ta.n(賦值將始終更新對象,除非該類明確禁止'__slots__,在這種情況下引發異常)。

您可以通過直接從類(例如, testa.a