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.
self.attr = attr or []
w ramach__init__
metody. Osiąga ten sam rezultat (tak mi się wydaje) i nadal jest jasny i czytelny.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
num
się 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
num
się 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ź
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
źródło
def __init__(self, num = None)
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.
źródło
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]
źródło
[]
że sklonowano tam tylko pierwszą osobę;x.l
iy.l
są zarówno złymi imionami, jak i różnymi bezpośrednimi wartościami związanymi z tym samym odnośnikiem. (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__
.źródło
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
dataclass
automatycznie 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)
źródło