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
представляется совместно (например, переменная класса). Я думаю, что я пропустил что-то важное...
Ответы
Ответ 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.