Подтвердить что ты не робот

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

Я думал, что energy и skill являются переменными класса, потому что я объявил их из любого метода. Я изменяю его значения внутри методов таким же образом (с self в его объявлении, может быть, неверно?). Но результаты показывают мне, что energy принимает разные значения для каждого объекта (например, переменную экземпляра), а skills представляется совместно (например, переменная класса). Я думаю, что я пропустил что-то важное...

4b9b3361

Ответ 1

У вас возникают проблемы с инициализацией, основанные на изменчивости.

Сначала, исправление. skills и energy являются атрибутами класса. Рекомендуется рассматривать их как прочитанные только как начальные значения атрибутов экземпляра. Классический способ создания вашего класса:

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

Затем каждый экземпляр будет иметь свои собственные атрибуты, все ваши проблемы исчезнут.

Второй, что происходит с этим кодом? Почему skills shared, когда energy - для каждого экземпляра?

Оператор -= является тонким. это возможно при назначении на месте. Разница здесь в том, что типы list изменяются, поэтому часто происходит модификация места:

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

[] 201781512
['strong'] 201781512

So a1.skills и a2.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

Ответ 2

Трюк здесь заключается в понимании того, что делает self.energy -= 1. Это действительно два выражения; один получает значение self.energy - 1 и один присваивает это обратно self.energy.

Но то, что вас сбивает с толку, заключается в том, что ссылки не интерпретируются одинаково с обеих сторон этого назначения. Когда Python сообщает, что он получает self.energy, он пытается найти этот атрибут на экземпляре, терпит неудачу и возвращается к атрибуту класса. Однако, когда он присваивает self.energy, он всегда будет присваивать атрибут экземпляра, даже если он ранее не существовал.

Ответ 3

При первоначальном создании оба атрибута являются одним и тем же объектом:

>>> 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, ссылка вернется к атрибуту класса:

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

Ответ 4

Доступ к переменным класса через класс, а не через 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)

Ответ 5

Собственно в коде   a1.work();   распечатать a1.energy;   print a2.energy

когда вы вызываете a1.work(), переменная экземпляра для объекта a1 создается с тем же именем, что и "энергия". И когда интерпретатор приходит к "print a1.energy", он выполняет переменную экземпляра объекта a1. И когда интерпретатор приходит к "print a2.energy", он выполняет переменную класса, и поскольку вы не изменили значение переменной класса, в качестве вывода он отображает 10.