Python:理解类和实例变量

Python:理解类和实例变量,python,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_

我想我对类和实例变量有一些误解。下面是一个示例代码:

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']

我认为
energy
skill
是类变量,因为我用任何方法声明了它们。我以同样的方式在方法中修改它的值(在他的声明中使用
self
,可能不正确?)。但是结果显示,
energy
对每个对象采用不同的值(如实例变量),而
skills
似乎是共享的(如类变量)。我想我错过了一些重要的事情…

这里的诀窍在于理解self.energy-=1的功能。这实际上是两种表达方式;一个获取
self.energy-1
的值,另一个将其分配回
self.energy

但让你困惑的是,在作业的两边,引用的解释并不相同。当Python被告知获取
self.energy
时,它会尝试在实例上找到该属性,失败后返回到class属性。但是,当它分配给self.energy时,它将始终分配给实例属性,即使该属性以前不存在。

实际上在您的代码中 a1.工作(); 打印a1.1能量; 打印a2.2能量

当您调用a1.work()时,a1对象的实例变量将被创建为具有相同名称的“energy”。 当解释器调用“print a1.energy”时,它执行对象a1的实例变量。
当解释器转到“print a2.energy”时,它将执行类变量,因为您没有更改类变量的值,所以它将10显示为输出。

通过类而不是通过self访问类变量:

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)

初始创建时,两个属性都是相同的对象:

>>> a1 = Animal()
>>> a2 = Animal()
>>> a1.energy is a2.energy
True
>>> a1.skills is a2.skills
True
>>> a1 is a2
False
当您指定给
属性时,它将成为实例的本地属性:

>>> 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
>>> id(a1.skills)
140668681481032
>>> a1.skills = ['sit', 'jump']
>>> id(a1.skills)
140668681617704
>>> id(a2.skills)
140668681481032
>>> a1.skills
['sit', 'jump']
>>> a2.skills
['bark', 'sleep']
new\u skill()
方法不会为
skills
数组指定新值,而是
附加了
,从而修改列表

如果要手动添加技能,则
技能
列表将位于实例的本地:

>>> 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
>>> 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
,则引用将恢复为类属性:

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

您遇到了基于可变性的初始化问题

首先是修复<代码>技能和
能量
是职业属性。 将它们视为只读,作为实例属性的初始值是一个很好的实践。构建类的经典方法是:

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

   ....
然后每个实例都有自己的属性,所有问题都会消失

第二个,此代码发生了什么变化? 为什么
技能
是共享的,而
能量
是每个实例的

-=
运算符很微妙。如果可能的话,这是就地分配。这里的区别在于
列表
类型是可变的,因此经常发生就地修改:

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

[] 201781512
['strong'] 201781512
因此
a1.技能
a2.技能
是相同的列表,也可以作为
Animal.skills
访问。但是能量是不可变的,所以修改是不可能的。在这种情况下,将创建一个新的
int
对象,因此每个实例都管理自己的
energy
变量副本:

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

10 1360251232
9 1360251200

这个问题比那个重复的问题更微妙,因为它询问的是两个类属性的行为之间的差异。我肯定还有一个复制品,但不是那个。是的,你有。能量是不可变的,分配给它会替换变量,但是在实例上,让类单独存在。另一方面,您并没有替换技能,而是添加到类的共享实例中。至于答案,您并没有像您声称的那样“以相同的方式修改值”。您使用
self.energy-=1
修改了能量,这是一个赋值;您使用
self.skills.append(…)
方法调用修改了
skills
。这些是不同的。@BrenBarn:当我点击伦敦隧道系统时,我正在寻找其他链接。@BrenBarn:我在寻找;这和这里发生的情况(不可变值和可变值)有点相反,这也是一个容易覆盖默认值的巧妙技巧。在类上有一个良好的默认值,但在实例上赋值只会覆盖该实例。在本例中,可能应该提到类之间的重影。
new\u skill()
方法。您能建议编写
new\u skill
方法的正确方法吗?我正在尽我最大的努力以开放的心态从C家族中学习,但是像这样的事情让我想知道python是如何变得如此流行的。(并不是说C和C衍生物没有缺点,但这是一些非常基本的类/封装材料。)我不确定你在问什么
new_skill
现在就可以了,不过你可以很容易地把它全部放下,从类外调用
Animal.skills.append('whatever')
。@Bmo这个方法很好。您只需将
skills
定义为实例属性,方法是将
self.skills=[]
放在
\uuuu init\uuuu
方法中(如果这是您要问的…),为什么不使用类名以提高可读性<代码>动物.技能.附加(技能)