Новый экземпляр класса с ненулевым атрибутом класса? - PullRequest
4 голосов
/ 26 октября 2011

У меня есть класс Python, у которого атрибут класса установлен в значение, отличное от None. При создании нового экземпляра изменения, внесенные в этот атрибут, сохраняются во всех экземплярах.

Вот некоторый код, чтобы понять это:

 class Foo(object):
   a = []
   b = 2

 foo = Foo()
 foo.a.append('item')
 foo.b = 5

Использование foo.a возвращает ['item'] и foo.b возвращает 5, как и следовало ожидать.

Когда я создаю новый экземпляр (назовем его bar), использование bar.a возвращает ['item'], а bar.b возвращает 5 тоже! Однако, когда я первоначально установил все атрибуты класса в None, затем установил их в __init__, например, так:

 class Foo(object):
   a = None
   b = None

   def __init__(self):
     self.a = []
     self.b = 2

Использование bar.a возвращает [] и bar.b возвращает 2, а foo.a возвращает ['item'] и foo.b возвращает 5.

Это как предположим, работать? Я очевидно никогда не сталкивался с этой проблемой в течение 3 лет, я программировал Python и хотел бы получить некоторые разъяснения. Я также не могу найти его где-нибудь в документации, поэтому, если возможно, дать мне ссылку было бы замечательно. :)

Ответы [ 4 ]

7 голосов
/ 26 октября 2011

Да, именно так оно и должно работать.

Если a и b принадлежат экземпляру из Foo, то правильный способ сделать этоis:

class Foo(object):
   def __init__(self):
     self.a = []
     self.b = 2

Следующее делает a и b принадлежащими самому классу , поэтому все экземпляры имеют одинаковые переменные:

class Foo(object):
   a = []
   b = 2

Когда вы смешиваете два метода - как вы это делали во втором примере - это не добавляет ничего полезного, а просто вызывает путаницу.

Стоит упомянуть одно предостережение: когда вы делаете следующее в первомпример:

foo.b = 5

вы не меняете Foo.b, вы добавляете новый атрибут к foo, который "затеняет" Foo.b.Когда вы делаете это, ни bar.b, ни Foo.b не изменяются.Если впоследствии вы выполните del foo.b, то этот атрибут будет удален, а foo.b снова будет ссылаться на Foo.b.

1 голос
/ 26 октября 2011

Да, это кажется удивительным в начале, но именно так работает питон, как сказано в других ответах.Три года питона, не будучи подрезанным этим?Вам повезло, очень повезло.На вашем месте я бы проверил чувствительный код прошлого для маленьких животных ...

Также будьте осторожны с вызовами функций: def func(list=[]) имеет похожее поведение, что может быть удивительным.

ИЕсли возможно, подключите анализ Pylint в ваших хуках коммитов.Или, по крайней мере, когда-нибудь запустить pylint в вашем коде, это очень поучительно, и, вероятно, расскажет вам об этом трюке (который не является трюком, на самом деле, он очень логичен и ортогональн, просто в Python).

1 голос
/ 26 октября 2011

Да, это именно то, что вы должны ожидать.Когда вы определяете переменную в классе, эти атрибуты присоединяются к классу, а не к экземпляру.Вы можете не всегда замечать, что когда вы назначаете атрибуту экземпляра, экземпляр получает новое значение, маскируя атрибут класса.Методы, которые изменяют на месте , например list.append, не дают экземпляру новый атрибут, поскольку они просто изменяют существующий объект, который оказывается атрибутом класса.

ЛюбойЕсли каждый экземпляр класса должен иметь свое собственное уникальное значение для атрибута, вы обычно должны устанавливать его в методе __init__, чтобы быть уверенным, что оно отличается для каждого экземпляра.

только тогда, когда класс имеетатрибут, который имеет разумное значение по умолчанию, и это значение имеет неизменный тип (который не может быть изменен на месте), например int или str, если вы установите атрибуты для класса.

0 голосов
/ 26 октября 2011

Вы путаете атрибут класса с атрибутом экземпляра.В вашем первом примере есть только атрибуты класса a и b, но во втором у вас также есть атрибуты экземпляра с теми же именами.

Похоже, что Python будет проходить через ссылки на атрибутиз экземпляра в класс, если нет атрибута экземпляра (сегодня я узнал что-то новое!).

Вы можете найти этот код поучительным:

class Foo:
  a = 1
  b = 2

  def __init__(self):
    self.a=3

print Foo.a   # => 1
print Foo().a # => 3
print Foo.b   # => 2
print Foo().b # => 2
...