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

Как объявить значения по умолчанию для переменных экземпляра в Python?

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

class Foo:
    num = 1

или как это?

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

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

bar = Foo()
bar.num += 1

- хорошо определенная операция.

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

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

4b9b3361

Ответ 1

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

Во-первых, это нормально:

>>> 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 имеют общий атрибут. Это часто нежелательно.

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

>>> 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 не может быть изменен. Я полагаю, причина в основном связана с хешированием.

Ответ 2

Два отрывка делают разные вещи, поэтому это не вопрос вкуса, а вопрос правильного поведения в вашем контексте. Документация 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 с классом Foo . Изменения распространяются!

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

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

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

Код в экспоненте 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

Ответ 3

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

>>> class c:
...     l = []
... 
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]

Ответ 4

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

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

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

Ответ 5

Вы также можете объявить переменные класса как 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
>>>

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