Як слід оголосити значення за замовчуванням для змінних екземпляра в 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

Додаток Б

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__(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, використаний у цьому прикладі, відрізняється від підкреслення___
init__underscore

Чи не повинен бути першим прикладомdef __init__(self, num = None)
PyWalker2797

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автоматично генерує кілька методів для вашого класу, наприклад, конструктор. Як зазначає документація, на яку посилається вище, "змінні-члени, які використовуються у цих згенерованих методах, визначаються за допомогою анотацій типу 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.