Rozważ następującą klasę:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
Moi współpracownicy określają to w następujący sposób:
class Person:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
Głównym tego powodem jest to, że ich wybrany edytor pokazuje właściwości autouzupełniania.
Osobiście nie lubię tego drugiego, ponieważ nie ma sensu, aby klasa miała ustawione właściwości None
.
Który byłby lepszą praktyką i z jakich powodów?
__init__
już zapewnia autouzupełnianie itp. Również użycieNone
zapobiega temu, aby IDE wniosło lepszy typ atrybutu, więc lepiej zamiast tego użyć rozsądnej wartości domyślnej (kiedy możliwy).typing
moduł, który pozwala ci dostarczać wskazówki do IDE i lintera, jeśli tego rodzaju rzeczy łaskoczą twoje fantazje ...self
. Nawet jeśliself.name
aniself.age
nie były przydzielane__init__
oni by nie pokazać się na przykładself
, tylko pokazać się w klasiePerson
.Odpowiedzi:
Nazywam tę drugą złą praktyką zasadą „nie robi się tak, jak myślisz”.
Pozycję twojego współpracownika można przepisać w następujący sposób: „Zamierzam stworzyć grupę statycznych quasi-globalnych zmiennych statycznych, do których nigdy nie ma dostępu, ale które zajmują miejsce w tabelach przestrzeni nazw różnych klas (
__dict__
), tylko po to, aby moje IDE zrobiło coś."źródło
-OO
dla tych, którzy tego potrzebują.1. Spraw, aby Twój kod był łatwy do zrozumienia
Kod jest odczytywany znacznie częściej niż pisany. Uprość zadanie opiekuna kodu (w przyszłym roku może to być Ty).
Nie wiem o żadnych twardych regułach, ale wolę, aby jakikolwiek stan przyszłej instancji był jasno określony. Upaść z
AttributeError
jest wystarczająco poważna. Gorzej widać brak jasności cyklu życia atrybutu instancji. Ilość gimnastyki umysłowej wymaganej do przywrócenia możliwych sekwencji wywołań, które prowadzą do przypisania atrybutu, może łatwo stać się trywialna, co prowadzi do błędów.Zwykle więc nie tylko definiuję wszystko w konstruktorze, ale również staram się ograniczyć liczbę zmiennych do zminimalizowania.
2. Nie mieszaj członków klasy i instancji
Wszystko, co zdefiniujesz bezpośrednio w
class
deklaracji, należy do klasy i jest wspólne dla wszystkich instancji klasy. Np. Kiedy zdefiniujesz funkcję wewnątrz klasy, staje się ona metodą, która jest taka sama dla wszystkich instancji. To samo dotyczy członków danych. Jest to całkowicie odmienne od atrybutów instancji, w których zwykle się definiuje__init__
.Elementy danych na poziomie klasy są najbardziej przydatne jako stałe:
źródło
self
i nie musząself
być przekazywane, podczas gdy metody na poziomie klasy są niezwiązane i są prostymi funkcjami, jak widać wdef
czasie, i akceptują instancję jako pierwszy argument. To są różne rzeczy.AttributeError
to dobry sygnał niż błąd. W przeciwnym razie połknąłbyś Brak i uzyskałbyś bezsensowne wyniki. Jest to szczególnie ważne w omawianym przypadku, w którym atrybuty są zdefiniowane w__init__
, więc brakujący atrybut (ale istniejący na poziomie klasy) może być spowodowany jedynie błędnym dziedziczeniem.None
a ta wartość nie ma sensu w momencie budowy instancji, masz problem w swojej architekturze i musisz przemyśleć cykl życia wartości atrybutu lub jego wartości początkowej. Zauważ, że definiując swoje atrybuty wcześnie, możesz wykryć taki problem jeszcze przed napisaniem reszty klasy, nie mówiąc już o uruchomieniu kodu.Osobiście definiuję członków w metodzie __ init __ (). Nigdy nie myślałem o zdefiniowaniu ich w części klasowej. Ale to, co zawsze robię: inicjuję wszystkie elementy w metodzie __ init__, nawet te niepotrzebne w metodzie __ init__.
Przykład:
Myślę, że ważne jest zdefiniowanie wszystkich członków w jednym miejscu. Dzięki temu kod jest bardziej czytelny. Niezależnie od tego, czy znajduje się w __ init __ (), czy na zewnątrz, nie jest tak ważne. Ważne jest jednak, aby zespół zastosował mniej więcej ten sam styl kodowania.
Och, i możesz zauważyć, że kiedykolwiek dodałem przedrostek „_” do zmiennych składowych.
źródło
_
: Aby wskazać, że jest prywatny! (Dlaczego tak wiele osób w tym wątku myli lub na wpół myli Python z innymi językami?)To zła praktyka. Nie potrzebujesz tych wartości, one zaśmiecają kod i mogą powodować błędy.
Rozważać:
źródło
Pójdę z „trochę jak dokumentacja, a następnie” i stwierdzę, że jest
None
to nieszkodliwe, o ile jest to zawsze , lub wąski zakres innych wartości, wszystkie niezmienne.Cuchnie atawizmem i nadmiernym przywiązaniem do języków pisanych statycznie. I nie ma to nic wspólnego z kodem. Ale ma to niewielki cel, który pozostaje w dokumentacji.
Dokumentuje to, jakie są oczekiwane imiona, więc jeśli połączę kod z kimś, a jedno z nas będzie miało „nazwę użytkownika”, a druga nazwa użytkownika, będzie to dla ludzi wskazówka, że rozeszliśmy się i nie używamy tych samych zmiennych.
Wymuszanie pełnej inicjalizacji jako zasady osiąga to samo w bardziej Pythoński sposób, ale jeśli jest w niej rzeczywisty kod
__init__
, zapewnia to wyraźniejsze miejsce do dokumentowania używanych zmiennych.Oczywiście BIG problem polega na tym, że kusi ludzi do inicjowania wartościami innymi niż
None
, co może być złe:pozostawia globalny ślad i nie tworzy instancji dla x.
Ale jest to bardziej dziwactwo w Pythonie jako całości niż w tej praktyce i powinniśmy już być na tym punkcie paranoiczni.
źródło