Jak należy zadeklarować wartości domyślne dla zmiennych instancji w Pythonie?


90

Czy powinienem podać członkom mojej klasy domyślne wartości w następujący sposób:

class Foo:
    num = 1

lub w ten sposób?

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

W tym pytaniu odkryłem, że w obu przypadkach

bar = Foo()
bar.num += 1

jest dobrze zdefiniowaną operacją.

Rozumiem, że pierwsza metoda da mi zmienną klasy, a druga nie. Jeśli jednak nie potrzebuję zmiennej klasy, a wystarczy ustawić wartość domyślną dla moich zmiennych instancji, czy obie metody są równie dobre? A może jeden z nich bardziej „pytoniczny” niż drugi?

Zauważyłem, że w tutorialu Django używają drugiej metody do deklarowania modeli. Osobiście uważam, że druga metoda jest bardziej elegancka, ale chciałbym wiedzieć, jaki jest „standardowy” sposób.

Odpowiedzi:


132

Rozszerzając odpowiedź bp, chciałem wam pokazać, co miał na myśli, mówiąc o typach niezmiennych.

Po pierwsze, jest w porządku:

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

Jednak działa to tylko dla niezmiennych (niezmiennych) typów. Jeśli wartość domyślna byłaby zmienna (co oznacza, że ​​można ją zastąpić), to miałoby miejsce:

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

Zauważ, że oba ai bmają wspólny atrybut. Jest to często niepożądane.

W Pythonie jest to sposób definiowania wartości domyślnych dla zmiennych instancji, gdy typ jest zmienny:

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

Powodem, dla którego mój pierwszy fragment kodu działa, jest to, że przy niezmiennych typach Python tworzy nową instancję, kiedy tylko chcesz. Jeśli potrzebujesz dodać 1 do 1, Python utworzy dla ciebie nową 2, ponieważ starej 1 nie można zmienić. Myślę, że powodem jest głównie haszowanie.


2
Niedawno natknąłem się na ten znacznie prostszy sposób implementacji wartości domyślnych dla zmiennych atrybutów. Po prostu napisz self.attr = attr or []w ramach __init__metody. Osiąga ten sam rezultat (tak mi się wydaje) i nadal jest jasny i czytelny.
Bill

4
Przepraszam ludzie. Zrobiłem więcej badań nad sugestią w moim powyższym komentarzu i najwyraźniej nie jest to to samo i nie jest zalecane .
Bill

Dlaczego klasa TestC ma puste nawiasy? Czy to dziedzictwo Pythona 2?
ChrisG

55

Te dwa fragmenty robią różne rzeczy, więc nie jest to kwestia gustu, ale kwestia właściwego zachowania w Twoim kontekście. Dokumentacja Pythona wyjaśnia różnicę, ale oto kilka przykładów:

Eksponat A

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

To wiąże numsię z instancjami Foo . Zmiana wprowadzona w tym polu nie jest propagowana do innych instancji.

A zatem:

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

Dowód rzeczowy B

class Bar:
  num = 1

To wiąże numsię z klasą Bar . Propagowane są zmiany!

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

Rzeczywista odpowiedź

Jeśli nie potrzebuję zmiennej klasy, ale wystarczy ustawić wartość domyślną dla moich zmiennych instancji, czy obie metody są równie dobre? A może jeden z nich bardziej „pytoniczny” niż drugi?

Kod na wystawie B jest po prostu nieprawidłowy: dlaczego miałbyś chcieć wiązać atrybut klasy (wartość domyślna przy tworzeniu instancji) z pojedynczą instancją?

Kod w eksponacie A jest w porządku.

Jeśli chcesz podać wartości domyślne dla zmiennych instancji w swoim konstruktorze, zrobiłbym to jednak:

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

...lub nawet:

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

... lub nawet: (preferowane, ale wtedy i tylko wtedy, gdy masz do czynienia z typami niezmiennymi!)

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

W ten sposób możesz:

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

1
czy metoda inicjalizacji użyta w tym przykładzie różni się od podkreślenia___
init__underscore

Nie powinien być pierwszym przykłademdef __init__(self, num = None)
PyWalker2797

6

Używanie członków klasy do podawania wartości domyślnych działa bardzo dobrze, o ile jesteś ostrożny, aby robić to tylko z niezmiennymi wartościami. Jeśli spróbujesz to zrobić za pomocą listy lub dyktu, byłoby to dość zabójcze. Działa również, gdy atrybut instancji jest odwołaniem do klasy, o ile domyślna wartość to Brak.

Widziałem tę technikę z powodzeniem używaną w repoze, który jest frameworkiem działającym na Zope. Zaletą jest nie tylko to, że gdy twoja klasa jest utrwalona w bazie danych, muszą być zapisane tylko atrybuty inne niż domyślne, ale także gdy musisz dodać nowe pole do schematu, wszystkie istniejące obiekty widzą nowe pole z domyślną wartością wartości bez konieczności zmiany przechowywanych danych.

Uważam, że działa również dobrze w bardziej ogólnym kodowaniu, ale to kwestia stylu. Użyj tego, z czego jesteś najszczęśliwszy.


3

Używanie członków klas jako domyślnych wartości zmiennych instancji nie jest dobrym pomysłem i po raz pierwszy widziałem, jak wspomniano o tym pomyśle. To działa w twoim przykładzie, ale w wielu przypadkach może się nie udać. Na przykład, jeśli wartość jest zmienna, mutacja jej w niezmodyfikowanej instancji zmieni wartość domyślną:

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

z wyjątkiem tego, []że sklonowano tam tylko pierwszą osobę; x.li y.lsą zarówno złymi imionami, jak i różnymi bezpośrednimi wartościami związanymi z tym samym odnośnikiem. (
Uzgodnione

1

Możesz również zadeklarować zmienne klasy jako Brak, co zapobiegnie propagacji. Jest to przydatne, gdy potrzebujesz dobrze zdefiniowanej klasy i chcesz zapobiec błędom AttributeErrors. Na przykład:

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

Również jeśli potrzebujesz ustawień domyślnych:

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

Oczywiście nadal postępuj zgodnie z informacjami w innych odpowiedziach na temat używania typów mutable vs immutable jako domyślnych w __init__.


0

Dzięki klasom danych , funkcji dodanej w Pythonie 3.7, istnieje teraz jeszcze jeden (całkiem wygodny) sposób na osiągnięcie ustawień domyślnych wartości w instancjach klas. Dekorator dataclassautomatycznie wygeneruje kilka metod w Twojej klasie, takich jak konstruktor. Jak zauważono w dokumentacji, do której odnośnik znajduje się powyżej, „[t] zmienne składowe używane w tych generowanych metodach są definiowane za pomocą adnotacji typu PEP 526 ”.

Biorąc pod uwagę przykład OP, moglibyśmy to zaimplementować w następujący sposób:

from dataclasses import dataclass

@dataclass
class Foo:
    num: int = 0

Konstruując obiekt typu tej klasy możemy opcjonalnie nadpisać wartość.

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.