Как мне объявить значения по умолчанию для переменных экземпляра в Python? - PullRequest
75 голосов
/ 21 апреля 2010

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

class Foo:
    num = 1

или как это?

class Foo:
    def __init__(self):
        self.num = 1

В этот вопрос Я обнаружил, что в обоих случаях

bar = Foo()
bar.num += 1

является четко определенной операцией.

Я понимаю, что первый метод даст мне переменную класса, а второй - нет. Однако, если мне не требуется переменная класса, а нужно только установить значение по умолчанию для моих переменных экземпляра, оба метода одинаково хороши? Или один из них более «питонический», чем другой?

Одна вещь, которую я заметил, заключается в том, что в уроке по Django они используют второй метод для объявления моделей. Лично я считаю, что второй метод более элегантен, но я бы хотел знать «Стандартный» способ.

Ответы [ 5 ]

112 голосов
/ 21 апреля 2010

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

Во-первых, все в порядке:

>>> class TestB():
...     def __init__(self, attr=1):
...         self.attr = attr
...     
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1

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

>>> class Test():
...     def __init__(self, attr=[]):
...         self.attr = attr
...     
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>> 

Обратите внимание, что оба атрибута a и b имеют общий атрибут.Это часто нежелательно.

Это способ Python для определения значений по умолчанию для переменных экземпляра, когда тип изменчив:

>>> class TestC():
...     def __init__(self, attr=None):
...         if attr is None:
...             attr = []
...         self.attr = attr
...     
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]

Причина, по которой мой первый фрагмент кода работает, заключается в том, чтос неизменяемыми типами Python создает его новый экземпляр всякий раз, когда вы этого захотите.Если вам нужно было добавить 1 к 1, Python создаст для вас новую 2, потому что старая 1 не может быть изменена.Я думаю, причина в основном в хешировании.

48 голосов
/ 21 апреля 2010

Два фрагмента делают разные вещи, так что дело не в вкусе, а в том, как правильно вести себя в вашем контексте. Документация Python объясняет разницу, но вот несколько примеров:

Приложение A

class Foo:
  def __init__(self):
    self.num = 1

Это связывает num с экземплярами Foo ,Изменение в этом поле не распространяется на другие экземпляры.

Таким образом:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

Приложение B

class Bar:
  num = 1

Это связывает num с баром класс .Изменения распространяются!

>>> bar1 = Bar()
>>> bar2 = Bar()
>>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation
>>> bar2.num
1
>>> Bar.num = 3
>>> bar2.num
3
>>> bar1.num
2
>>> bar1.__class__.num
3

Фактический ответ

Если мне не требуется переменная класса, а нужно только установить значение по умолчанию для переменных моего экземпляра, оба метода одинаковохорошо?Или один из них более «питонический», чем другой?

Код в экспонате B совершенно неверен для этого: зачем вам связывать атрибут класса (значение по умолчанию при создании экземпляра) содин экземпляр?

Код в приложении A в порядке.

Если вы хотите задать значения по умолчанию для переменных экземпляра в вашем конструкторе, я бы сделал это:

class Foo:
  def __init__(num = None):
    self.num = num if num is not None else 1

... или даже:

class Foo:
  DEFAULT_NUM = 1
  def __init__(num = None):
    self.num = num if num is not None else DEFAULT_NUM

... или даже: (предпочтительно, но если и только если вы имеете дело с неизменяемыми типами!)

class Foo:
  def __init__(num = 1):
    self.num = num

Таким образом, вы можетеделать:

foo1 = Foo(4)
foo2 = Foo() #use default
4 голосов
/ 21 апреля 2010

Использование членов класса для задания значений по умолчанию работает очень хорошо, лишь бы вы старались делать это только с неизменяемыми значениями. Если вы попытаетесь сделать это с помощью списка или указания, это будет довольно смертельно. Это также работает, когда атрибут экземпляра является ссылкой на класс, если значением по умолчанию является None.

Я видел, как эта техника очень успешно использовалась в repoze, которая является фреймворком, работающим поверх Zope. Преимущество здесь не только в том, что когда ваш класс сохраняется в базе данных, нужно сохранять только атрибуты, отличные от заданных по умолчанию, но также и когда вам нужно добавить новое поле в схему, все существующие объекты видят новое поле с его значением по умолчанию значение без необходимости фактически изменять сохраненные данные.

Я считаю, что это также хорошо работает в более общем кодировании, но это стиль. Используйте то, что вам больше всего нравится.

3 голосов
/ 21 апреля 2010

Использование членов класса для значений по умолчанию переменных экземпляра не очень хорошая идея, и я впервые увидел эту идею упомянутой вообще. Это работает в вашем примере, но во многих случаях может дать сбой. Например, если значение является изменяемым, изменение его в неизмененном экземпляре изменит значение по умолчанию:

>>> class c:
...     l = []
... 
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]
1 голос
/ 28 мая 2015

Вы также можете объявить переменные класса как None, что предотвратит распространение. Это полезно, когда вам нужен четко определенный класс и вы хотите предотвратить AttributeErrors. Например:

>>> class TestClass(object):
...     t = None
... 
>>> test = TestClass()
>>> test.t
>>> test2 = TestClass()
>>> test.t = 'test'
>>> test.t
'test'
>>> test2.t
>>>

Также, если вам нужны значения по умолчанию:

>>> class TestClassDefaults(object):
...    t = None
...    def __init__(self, t=None):
...       self.t = t
... 
>>> test = TestClassDefaults()
>>> test.t
>>> test2 = TestClassDefaults([])
>>> test2.t
[]
>>> test.t
>>>

Конечно, все еще следуйте информации в других ответах об использовании изменяемых и неизменяемых типов по умолчанию в __init__.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...