2016-03-03 77 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