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


90

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

class Foo:
    num = 1

или вот так?

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

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

bar = Foo()
bar.num += 1

это четко определенная операция.

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

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

Ответы:


132

Расширяя ответ 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
Недавно я наткнулся на гораздо более простой способ реализации значений по умолчанию для изменяемых атрибутов. Просто напишите self.attr = attr or []в рамках __init__метода. Он дает тот же результат (я думаю), но при этом остается четким и читаемым.
Билл

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

Почему у класса TestC пустые скобки? Это наследие Python 2?
ChrisG,

55

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

Выставка

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к классу Bar . Изменения распространяются!

>>> 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 явно неверен для этого: зачем вам связывать атрибут класса (значение по умолчанию при создании экземпляра) с единственным экземпляром?

Код на выставке А в порядке.

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

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

...или даже:

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

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

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

Таким образом вы можете:

foo1 = Foo(4)
foo2 = Foo() #use default

1
отличается ли метод инициализации, используемый в этом примере, от подчеркивания___
init__underscore

def __init__(self, num = None)
Разве

6

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

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

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


3

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

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

за исключением того, что там []были клонированы только самые первые ; x.lи y.lявляются одновременно злыми именами и разными непосредственными ценностями, связанными с одним и тем же референтом. (Условия языкового юриста составлены)
Филип

1

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


0

С помощью классов данных , функции, добавленной в Python 3.7, теперь есть еще один (довольно удобный) способ добиться установки значений по умолчанию для экземпляров класса. Декоратор dataclassавтоматически сгенерирует несколько методов вашего класса, например конструктор. Как отмечается в документации, приведенной выше, «[t] переменные-члены для использования в этих сгенерированных методах определены с использованием аннотаций типа PEP 526 ».

Рассматривая пример OP, мы могли бы реализовать это так:

from dataclasses import dataclass

@dataclass
class Foo:
    num: int = 0

При построении объекта этого типа класса мы можем при желании перезаписать значение.

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.