2016-03-03 138 views
54

我想我對類和實例變量有一些誤解。下面是一個例子代碼:Python:理解類和實例變量

class Animal(object): 
    energy = 10 
    skills = [] 

    def work(self): 
     print 'I do something' 
     self.energy -= 1 

    def new_skill(self, skill): 
     self.skills.append(skill) 


if __name__ == '__main__': 

    a1 = Animal() 
    a2 = Animal() 

    a1.work() 
    print a1.energy # result:9 
    print a2.energy # result:10 


    a1.new_skill('bark') 
    a2.new_skill('sleep') 
    print a1.skills # result:['bark', 'sleep'] 
    print a2.skills # result:['bark', 'sleep'] 

我認爲energyskill是類變量,因爲我宣佈出來的任何方法。我以相同的方式在方法內修改它的值(在他的聲明中使用self,可能不正確?)。但結果表明,energy爲每個對象(如實例變量)取不同的值,而skills似乎是共享的(就像一個類變量)。我想我已經錯過了一些重要的...

+4

這個問題比重複更微妙的,因爲它問的二級屬性的行爲之間的區別。我相信重複仍然存在,但不是那個。 – BrenBarn

+4

是的,你做到了。能量是不可變的,分配給它取代變量,但是在實例中,只剩下班級。另一方面,你不是在替換技能,而是在課堂上添加共享實例。 –

+2

至於答案,你沒有像你聲稱的那樣「以相同的方式修改數值」。你用'self.energy - = 1'來修改能量,作業;你用'self.skills.append(...)'方法調用修改'skills'。那些是不同的。 – BrenBarn

回答

32

您正在運行基於可變性的初始化問題。

首先,該修補程序。 skillsenergy是類屬性。 將它們視爲只讀屬性是實例屬性的初始值,這是一種很好的做法。建立你的課程的經典方法是:

class Animal(object): 
    energy = 10 
    skills = [] 
    def __init__(self,en=energy,sk=skills): 
     self.energy=en 
     self.skills=sk 
    .... 

然後每個實例都有自己的屬性,所有的問題都會消失。

,發生了什麼與此代碼? 爲什麼skills共享,當energy是每個實例?

-=操作是很微妙的。它是就地分配如果可能。這裏的區別是,list類型是可變的,所以就地修改經常發生:

In [6]: 
    b=[] 
    print(b,id(b)) 
    b+=['strong'] 
    print(b,id(b)) 

[] 201781512 
['strong'] 201781512 

所以a1.skillsa2.skills是相同的列表,這也爲Animal.skills訪問。但是energy是一個不可變的int,所以修改是不可能的。在這種情況下,一個新的int對象被創建,所以每個實例都管理自己的energy變量的副本:

In [7]: 
    a=10 
    print(a,id(a)) 
    a-=1 
    print(a,id(a)) 

10 1360251232 
9 1360251200 
38

這裏的訣竅是瞭解什麼self.energy -= 1做什麼。這實際上是兩種表達方式;一個獲得self.energy - 1的值,另一個返回self.energy

但是讓你感到困惑的是,在作業的兩邊都沒有以相同的方式解釋引用。當Python被告知獲得self.energy時,它會嘗試在實例上找到該屬性,失敗並返回到類屬性。但是,當它指定給self.energy時,它將始終分配給實例屬性,即使此屬性以前不存在。

+0

這也是輕鬆覆蓋默認值的巧妙方法。在類上有一個很好的默認值,但在一個實例上進行分配會覆蓋該實例。 –

+0

應該提到這個例子中類之間的'new_skill()'鬼影。 – Torxed

+0

你可以建議編寫'new_skill'方法的正確方法嗎?我盡我所能地以開放的態度從C族開始,但這樣的事情讓我想知道python是如何受歡迎的。 (不是說C和C衍生物沒有缺點,但是這是一些非常基本的類/封裝的東西。) – Bmo

3

其實在你的代碼 a1.work(); print a1.energy; print a2.energy

當您調用a1.work()時,會創建一個名爲'energy'的同名對象的實例變量。 而當解釋器來到'打印a1.energy'時,它執行對象a1的實例變量。 而當解釋器來到「打印a2.energy」它執行類變量,並且因爲你沒有改變類變量的值,它顯示10作爲輸出。

6

訪問通過類的類變量,而不是通過自我:

class Animal(object): 
    energy = 10 
    skills = [] 

    def work(self): 
     print 'I do something' 
     self.__class__.energy -= 1 

    def new_skill(self, skill): 
     self.__class__.skills.append(skill) 
+0

爲什麼不使用類名來提高可讀性? 'Animal.skills.append(技能)' – bddap

23

在最初創作這兩個屬性是相同的對象:

>>> a1 = Animal() 
>>> a2 = Animal() 
>>> a1.energy is a2.energy 
True 
>>> a1.skills is a2.skills 
True 
>>> a1 is a2 
False 

當你分配class屬性,它是由本地實例產生的:

>>> id(a1.energy) 
31346816 
>>> id(a2.energy) 
31346816 
>>> a1.work() 
I do something 
>>> id(a1.energy) 
31346840 # id changes as attribute is made local to instance 
>>> id(a2.energy) 
31346816 

new_skill()方法不爲分配skills數組的新值,而是它appends哪些修改列表到位。

如果你手動添加一個技能,那麼skills名單將前來本地實例:

>>> id(a1.skills) 
140668681481032 
>>> a1.skills = ['sit', 'jump'] 
>>> id(a1.skills) 
140668681617704 
>>> id(a2.skills) 
140668681481032 
>>> a1.skills 
['sit', 'jump'] 
>>> a2.skills 
['bark', 'sleep'] 

最後,如果你要刪除的實例屬性a1.skills,參考將恢復到class屬性:

>>> a1.skills 
['sit', 'jump'] 
>>> del a1.skills 
>>> a1.skills 
['bark', 'sleep'] 
>>> id(a1.skills) 
140668681481032