Dlaczego projektanci Pythona zdecydowali, że __init__()
metody podklas nie wywołują automatycznie __init__()
metod ich nadklas, jak w niektórych innych językach? Czy idiom w języku Pythonic i zalecane jest naprawdę podobny do następującego?
class Superclass(object):
def __init__(self):
print 'Do something'
class Subclass(Superclass):
def __init__(self):
super(Subclass, self).__init__()
print 'Do something else'
python
inheritance
subclass
delegation
superclass
jrdioko
źródło
źródło
__init__
metodę, a nawet może automatycznie wyszukać podklasy i je ozdobić.Odpowiedzi:
Kluczowa różnica między Pythona
__init__
i tych innych języków konstruktorów jest to, że__init__
to nie konstruktor: to jest inicjator (rzeczywisty konstruktor (jeśli w ogóle, ale zobaczymy później ;-) jest__new__
i działa zupełnie inaczej znowu). Podczas konstruowania wszystkie superklas (i bez wątpienia robi tak „przed” ty kontynuować budowy w dół) jest oczywiście częścią mówiąc jesteś konstruowaniu instancji podklasy jest, że z pewnością nie jest przypadek inicjalizacji, ponieważ istnieje wiele przypadków użycia, w których inicjalizacja nadklas musi zostać pominięta, zmieniona, kontrolowana - dzieje się, jeśli w ogóle, „w środku” inicjalizacji podklasy i tak dalej.Zasadniczo, super-klasa delegacja inicjatora nie jest automatyczna w Pythonie dla dokładnie tych samych powodów takie przekazanie nie jest także automatyczne do jakichkolwiek innych metod - i zauważ, że te „inne języki” nie rób automatycznego delegację super-klasę dla każdego inna metoda albo ... tylko dla konstruktora (i jeśli to możliwe, destruktora), który, jak wspomniałem, nie jest tym, czym jest Python
__init__
. (Zachowanie__new__
jest również dość osobliwe, chociaż tak naprawdę nie jest bezpośrednio związane z twoim pytaniem, ponieważ__new__
jest tak osobliwym konstruktorem, że właściwie nie musi niczego konstruować - może z powodzeniem zwrócić istniejącą instancję lub nawet nie instancję ... oczywiście Python oferuje wielewiększą kontrolę nad mechaniką niż w przypadku „innych języków”, o których myślisz, co oznacza również brak możliwości automatycznego delegowania__new__
zadań! -).źródło
__init__
nie jest konstruktorem ... rzeczywisty konstruktor ... jest__new__
". Jak sam zauważyłeś,__new__
nie zachowuje się jak konstruktor z innych języków.__init__
jest w rzeczywistości bardzo podobny (jest wywoływany podczas tworzenia nowego obiektu, po jego przydzieleniu, aby ustawić zmienne składowe w nowym obiekcie) i prawie zawsze jest miejscem do zaimplementowania funkcjonalności, które w innych językach byś umieścił konstruktor. Więc nazwij to konstruktorem!__new__
nie wywołuje też automatycznie superklasy__new__
. Więc twoje twierdzenie, że kluczową różnicą jest to, że konstrukcja koniecznie obejmuje budowę nadklas, podczas gdy inicjalizacja nie jest niezgodna z twoim twierdzeniem, że__new__
jest konstruktorem.__init__
na docs.python.org/reference/datamodel.html#basic-customization : „Jako specjalny przymusu na konstruktorów , bez wartości mogą zostać zwrócone; spowoduje to spowodować TypeError zostać podniesiona przy starcie „(podkreślenie moje). Więc to oficjalne,__init__
jest konstruktor.__init__
jest nazywany konstruktorem. Ten konstruktor jest funkcją inicjalizacyjną wywoływaną po całkowitym skonstruowaniu obiektu i zainicjowaniu go do stanu domyślnego, w tym jego ostatecznego typu środowiska uruchomieniowego. Nie jest to odpowiednik konstruktorów C ++, które są wywoływane na statycznie typowanym, przydzielonym obiekcie o niezdefiniowanym stanie. Są one również różne od__new__
, więc tak naprawdę mamy co najmniej cztery różne rodzaje funkcji alokacji / konstrukcji / inicjalizacji. Języki używają mieszanej terminologii, a ważną częścią jest zachowanie, a nie terminologia.Jestem trochę zawstydzony, gdy ludzie papugują „zen Pythona”, jakby to wszystko usprawiedliwiało. To filozofia projektowania; poszczególne decyzje projektowe zawsze można wyjaśnić bardziej konkretnymi terminami - i tak musi być, bo inaczej „Zen Pythona” staje się wymówką do robienia czegokolwiek.
Powód jest prosty: niekoniecznie konstruujesz klasę pochodną w sposób podobny do tego, jak konstruujesz klasę bazową. Możesz mieć więcej parametrów, mniej, mogą być w innej kolejności lub w ogóle nie być powiązane.
Dotyczy to wszystkich metod pochodnych, a nie tylko
__init__
.źródło
Java i C ++ wymagają wywołania konstruktora klasy bazowej ze względu na układ pamięci.
Jeśli masz klasę
BaseClass
z członkiemfield1
i tworzysz nową klasę,SubClass
która dodaje członkafield2
, to wystąpienieSubClass
zawiera miejsce nafield1
ifield2
. Potrzebujesz konstruktoraBaseClass
do wypełnieniafield1
, chyba że chcesz, aby wszystkie klasy dziedziczące powtarzałyBaseClass
inicjalizację we własnych konstruktorach. A jeślifield1
jest prywatny, dziedziczenie klas nie może zostać zainicjowanefield1
.Python to nie Java ani C ++. Wszystkie wystąpienia wszystkich klas zdefiniowanych przez użytkownika mają ten sam „kształt”. Są to po prostu słowniki, w których można wstawiać atrybuty. Przed wykonaniem jakiejkolwiek inicjalizacji wszystkie wystąpienia wszystkich klas zdefiniowanych przez użytkownika są prawie dokładnie takie same ; to tylko miejsca do przechowywania atrybutów, które jeszcze nie przechowują.
Dlatego też dla podklasy Pythona ma sens, aby nie wywoływać konstruktora klasy bazowej. Mógłby po prostu dodać atrybuty, gdyby chciał. Nie ma miejsca zarezerwowanego dla danej liczby pól dla każdej klasy w hierarchii i nie ma różnicy między atrybutem dodanym przez kod z
BaseClass
metody a atrybutem dodanym przez kod zSubClass
metody.Jeśli, co jest powszechne,
SubClass
faktycznie chce mieć wszystkieBaseClass
niezmienniki ustawione przed przystąpieniem do własnych dostosowań, to tak, możesz po prostu zadzwonićBaseClass.__init__()
(lub użyćsuper
, ale jest to skomplikowane i czasami ma swoje własne problemy). Ale nie musisz. Możesz to zrobić przed, po lub z różnymi argumentami. Do diabła, gdybyś chciał, mógłbyś wywołać theBaseClass.__init__
z innej metody całkowicie niż__init__
; może masz jakieś dziwne, leniwe inicjowanie.Python osiąga tę elastyczność dzięki prostocie. Obiekty inicjalizujesz, pisząc
__init__
metodę, która ustawia atrybutyself
. Otóż to. Zachowuje się dokładnie jak metoda, ponieważ jest dokładnie metodą. Nie ma innych dziwnych i nieintuicyjnych zasad dotyczących rzeczy, które trzeba zrobić najpierw lub rzeczy, które wydarzy się automatycznie, jeśli nie zrobisz innych rzeczy. Jedynym celem, jaki musi spełniać, jest być punktem zaczepienia do wykonania podczas inicjalizacji obiektu w celu ustawienia początkowych wartości atrybutów, i właśnie to robi. Jeśli chcesz, aby robił coś innego, napisz to wyraźnie w swoim kodzie.źródło
„Wyraźne jest lepsze niż ukryte”. To samo rozumowanie wskazuje, że powinniśmy wyraźnie napisać „ja”.
Myślę, że w końcu jest to korzyść - czy możesz wyrecytować wszystkie zasady Java dotyczące wywoływania konstruktorów nadklas?
źródło
Często podklasa ma dodatkowe parametry, których nie można przekazać do nadklasy.
źródło
W tej chwili mamy dość długą stronę opisującą kolejność rozwiązywania metod w przypadku wielokrotnego dziedziczenia: http://www.python.org/download/releases/2.3/mro/
Gdyby konstruktory były wywoływane automatycznie, potrzebna byłaby kolejna strona o co najmniej tej samej długości, wyjaśniająca kolejność tego procesu. To byłoby piekło ...
źródło
Aby uniknąć nieporozumień, warto wiedzieć, że można wywołać
__init__()
metodę base_class , jeśli child_class nie ma__init__()
klasy.Przykład:
W rzeczywistości MRO w Pythonie będzie szukał
__init__()
w klasie nadrzędnej, gdy nie może go znaleźć w klasie dzieci. Jeśli masz już__init__()
metodę w klasie podrzędnej, musisz bezpośrednio wywołać konstruktor klasy nadrzędnej .Na przykład następujący kod zwróci błąd: class parent: def init (self, a = 1, b = 0): self.a = a self.b = b
źródło
Może
__init__
jest to metoda, którą podklasa musi zastąpić. Czasami podklasy wymagają uruchomienia funkcji rodzica przed dodaniem kodu specyficznego dla klasy, a innym razem muszą skonfigurować zmienne instancji przed wywołaniem funkcji nadrzędnej. Ponieważ nie ma sposobu, aby Python mógł wiedzieć, kiedy byłoby najbardziej odpowiednie wywołanie tych funkcji, nie powinien zgadywać.Jeśli to Cię nie kołysze, pomyśl, że
__init__
to tylko inna funkcja. Jeślidostuff
zamiast tego byłaby ta funkcja , czy nadal chciałbyś, aby Python automatycznie wywoływał odpowiednią funkcję z klasy nadrzędnej?źródło
Uważam, że jedną z bardzo ważnych kwestii jest tutaj to, że w przypadku automatycznego wywołania
super.__init__()
, zgodnie z projektem, wykluczasz, kiedy ta metoda inicjalizacji jest wywoływana i z jakimi argumentami. unikanie automatycznego wywoływania go i wymaganie od programisty jawnego wykonania tego wywołania wiąże się z dużą elastycznością.w końcu tylko dlatego, że klasa B wywodzi się z klasy A, nie oznacza, że
A.__init__()
można lub należy ją wywołać z tymi samymi argumentami, coB.__init__()
. uczynienie wywołania jawnym oznacza, że programista może np. zdefiniowaćB.__init__()
z zupełnie innymi parametrami, wykonać jakieś obliczenia z tymi danymi, wywołaćA.__init__()
z argumentami stosownie do tej metody, a następnie wykonać pewne przetwarzanie końcowe. ten rodzaj elastyczności byłby niewygodny do osiągnięcia, gdybyA.__init__()
był wywoływanyB.__init__()
niejawnie, albo przedB.__init__()
wykonaniem, albo zaraz po nim.źródło