Właśnie próbuję usprawnić jedną z moich klas i wprowadziłem pewne funkcje w tym samym stylu, co wzornictwo wagi lekkiej .
Jednak jestem trochę zdezorientowany, dlaczego __init__
zawsze jest wywoływany po __new__
. Nie spodziewałem się tego. Czy ktoś może mi powiedzieć, dlaczego tak się dzieje i jak mogę zaimplementować tę funkcję w inny sposób? (Oprócz umieszczenia implementacji w tym, __new__
co wydaje się dość hackerskie.)
Oto przykład:
class A(object):
_dict = dict()
def __new__(cls):
if 'key' in A._dict:
print "EXISTS"
return A._dict['key']
else:
print "NEW"
return super(A, cls).__new__(cls)
def __init__(self):
print "INIT"
A._dict['key'] = self
print ""
a1 = A()
a2 = A()
a3 = A()
Wyjścia:
NEW
INIT
EXISTS
INIT
EXISTS
INIT
Dlaczego?
Odpowiedzi:
Od kwietnia 2008 r .: Kiedy używać
__new__
Vs.__init__
? na mail.python.org.Powinieneś wziąć pod uwagę, że to, co próbujesz zrobić, zwykle odbywa się w Fabryce i jest to najlepszy sposób, aby to zrobić. Używanie
__new__
nie jest dobrym, czystym rozwiązaniem, więc rozważ użycie fabryki. Tutaj masz dobry przykład fabryki .źródło
__new__
klasy Factory, która stała się dość czysta, więc dziękuję za twój wkład.__new__
powinno być ściśle ograniczone do podanych przypadków. Uważam, że jest to bardzo przydatne do implementowania rozszerzalnych fabryk klas ogólnych - zobacz moją odpowiedź na pytanie Niewłaściwe użycie__new__
do generowania klas w Pythonie? na przykład tego.__new__
jest metodą klasy statycznej, podczas gdy__init__
jest metodą instancji.__new__
musi najpierw utworzyć instancję, więc__init__
może ją zainicjować. Uwaga, że__init__
trzebaself
jako parametr. Dopóki nie utworzysz wystąpienia, nie maself
.Rozumiem, że próbujesz wdrożyć wzorzec singletonu w Pythonie. Można to zrobić na kilka sposobów.
Ponadto, począwszy od Python 2.6, można używać dekoratorów klas .
źródło
MyClass
to powiązanie nazwy z funkcją, która zwraca instancje oryginalnej klasy. Ale teraz nie ma żadnego sposobu na odwołanie się do oryginalnej klasy, aMyClass
bycie funkcją psujeisinstance
/issubclass
sprawdza, dostęp do atrybutów / metod klasy bezpośrednio jakoMyClass.something
, nazwanie klasy wsuper
wywołaniach itp. Itp. To, czy jest to problem, czy nie, zależy od klasa, do której aplikujesz (i resztę programu).W najbardziej znanych językach OO wyrażenie takie
SomeClass(arg1, arg2)
przydzieli nową instancję, zainicjuje atrybuty instancji, a następnie zwróci ją.W najbardziej znanych językach OO część „inicjowanie atrybutów instancji” można dostosować dla każdej klasy poprzez zdefiniowanie konstruktora , który jest w zasadzie tylko blokiem kodu działającym na nowej instancji (przy użyciu argumentów przekazanych do wyrażenia konstruktora ), aby skonfigurować dowolne warunki początkowe. W Pythonie odpowiada to
__init__
metodzie klasy .Python
__new__
jest niczym więcej i niczym innym jak podobnym dostosowaniem dla poszczególnych klas części „przydzielanie nowej instancji”. To oczywiście pozwala robić niezwykłe rzeczy, takie jak zwracanie istniejącej instancji zamiast przydzielania nowej. Tak więc w Pythonie nie powinniśmy naprawdę myśleć o tej części jako o konieczności alokacji; wszystko, czego potrzebujemy, to__new__
wymyślenie odpowiedniej instancji skądś.Ale wciąż jest to tylko połowa zadania, a system Python nie może wiedzieć, że czasami chcesz uruchomić drugą połowę zadania (
__init__
), a czasem nie. Jeśli chcesz tego zachowania, musisz to powiedzieć wprost.Często można dokonać refaktoryzacji, aby wystarczyła
__new__
lub nie była potrzebna__new__
, lub aby__init__
zachowywała się inaczej na już zainicjowanym obiekcie. Ale jeśli naprawdę chcesz, Python pozwala ci na nowo zdefiniować „zadanie”, więcSomeClass(arg1, arg2)
niekoniecznie musi to być wywołanie,__new__
a następnie__init__
. Aby to zrobić, musisz utworzyć metaklasę i zdefiniować jej__call__
metodę.Metaklasa to tylko klasa klasy. A
__call__
metoda klasy kontroluje, co się stanie, gdy wywołasz instancje klasy. Więc metaklasa "__call__
kontroli sposobu, co się dzieje, kiedy zadzwonić klasę; tzn. pozwala przedefiniować mechanizm tworzenia instancji od początku do końca . Na tym poziomie możesz najbardziej elegancko wdrożyć całkowicie niestandardowy proces tworzenia instancji, taki jak wzorzec singletonu. W rzeczywistości, z mniej niż 10 linii kodu można zaimplementowaćSingleton
metaklasa że wtedy nawet nie wymaga futz ze__new__
w ogóle , a może zamienić dowolny inny sposób normalny do klasy Singleton poprzez dodanie__metaclass__ = Singleton
!Jest to jednak prawdopodobnie głębsza magia, niż jest to naprawdę uzasadnione w tej sytuacji!
źródło
Aby zacytować dokumentację :
źródło
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.
To ważny punkt. Jeśli zwrócisz inną instancję, oryginał__init__
nigdy nie zostanie wywołany.__new__
sprawdzany jest zwracany obiekt klasy, metoda generowałaby błąd, a nie pozwalała, aby sprawdzanie zakończone niepowodzeniem przebiegło bezgłośnie, ponieważ nie rozumiem, dlaczego miałbyś chcieć zwracać cokolwiek innego niż obiekt klasy .Zdaję sobie sprawę, że to pytanie jest dość stare, ale miałem podobny problem. Następujące czynności zrobiły, co chciałem:
Użyłem tej strony jako zasobu http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html
źródło
Myślę, że prosta odpowiedź na to pytanie jest taka, że jeśli
__new__
zwróci wartość tego samego typu co klasa,__init__
funkcja zostanie wykonana, w przeciwnym razie nie. W takim przypadku kod zwracaA._dict('key')
tę samą klasęcls
, co__init__
zostanie wykonany.źródło
Gdy
__new__
zwraca instancję tej samej klasy,__init__
jest następnie uruchamiana na zwróconym obiekcie. To znaczy, że NIE możesz użyć,__new__
aby zapobiec__init__
uruchomieniu. Nawet jeśli zwrócisz wcześniej utworzony obiekt__new__
, będzie on podwójnie (potrójny itp.) Inicjowany__init__
wielokrotnie.Oto ogólne podejście do wzorca Singleton, które rozszerza odpowiedź vartec powyżej i naprawia ją:
Pełna historia jest tutaj .
Innym podejściem, które w rzeczywistości wymaga
__new__
użycia metod klas:Proszę zwrócić uwagę, że przy takim podejściu musisz udekorować WSZYSTKIE swoje metody
@classmethod
, ponieważ nigdy nie użyjesz żadnego prawdziwego wystąpieniaMyClass
.źródło
Odnosząc się do tego dokumentu :
Jeśli chodzi o to, co chcesz osiągnąć, w tym samym dokumencie znajdują się również informacje o wzorze Singleton
możesz również użyć tej implementacji z PEP 318, używając dekoratora
źródło
init
z__new__
dźwięków naprawdę hacky. Po to są metaklasy.wyjścia:
NB jako efekt uboczny
M._dict
nieruchomość automatycznie staje się dostępny zA
takA._dict
więc zadbać, aby nie nadpisać go przypadkowo.źródło
__init__
metodycls._dict = {}
. Prawdopodobnie nie chcesz, aby słownik był współużytkowany przez wszystkie klasy tego typu metaty (ale +1 za pomysł).__new__ powinien zwrócić nową, pustą instancję klasy. __init__ jest następnie wywoływany w celu zainicjowania tego wystąpienia. Nie dzwonisz do __init__ w przypadku „NOWEGO” przypadku __new__, więc jest to dla ciebie. Wywoływany kod
__new__
nie śledzi, czy __init__ został wywołany w konkretnej instancji, czy też nie, ani nie powinien, ponieważ robisz tutaj coś bardzo niezwykłego.Możesz dodać atrybut do obiektu w funkcji __init__, aby wskazać, że został on zainicjowany. Sprawdź istnienie tego atrybutu jako pierwszą rzecz w __init__ i nie kontynuuj, jeśli tak było.
źródło
Aktualizacja odpowiedzi @AntonyHatchkins, prawdopodobnie potrzebujesz osobnego słownika instancji dla każdej klasy metatypy, co oznacza, że powinieneś mieć
__init__
metodę w metaklasie, aby zainicjować obiekt klasy za pomocą tego słownika, zamiast uczynić go globalnym dla wszystkich klas.Posunąłem się naprzód i zaktualizowałem oryginalny kod za pomocą
__init__
metody i zmieniłem składnię na notację w języku Python 3 (wywołanie no-argsuper
i metaklasa w argumentach klasy zamiast jako atrybut).Tak czy inaczej, ważnym punktem tutaj jest to, że inicjator klasy (
__call__
metoda) nie wykona się__new__
ani,__init__
jeśli klucz zostanie znaleziony. Jest to o wiele czystsze niż używanie__new__
, które wymaga oznaczenia obiektu, jeśli chcesz pominąć domyślny__init__
krok.źródło
Zagłębiam się trochę głębiej w to!
Typem ogólnej klasy w CPython jest
type
jej klasa podstawowaObject
(chyba że wyraźnie zdefiniujesz inną klasę podstawową, taką jak metaklasa). Sekwencję wywołań niskiego poziomu można znaleźć tutaj . Pierwsza wywoływana metoda to ta,type_call
która następnie wywołuje,tp_new
a następnietp_init
.Interesujące jest to, że
tp_new
wywołaObject
nową metodę (klasy bazowej),object_new
która wykonuje funkcjętp_alloc
(PyType_GenericAlloc
), która przydziela pamięć dla obiektu :)W tym momencie obiekt jest tworzony w pamięci, a następnie
__init__
wywoływana jest metoda. Jeśli__init__
nie jest zaimplementowany w twojej klasie,object_init
zostaje wywołany i nic nie robi :)Następnie
type_call
po prostu zwraca obiekt, który wiąże się ze zmienną.źródło
Należy patrzeć na
__init__
prostego konstruktora w tradycyjnych językach OO. Na przykład, jeśli znasz język Java lub C ++, konstruktor jest domyślnie przekazywany wskaźnikowi do własnej instancji. W przypadku Javy jest tothis
zmienna. Gdyby sprawdzić kod bajtowy wygenerowany dla Javy, można by zauważyć dwa wywołania. Pierwsze wywołanie dotyczy metody „nowej”, a następnie następne wywołanie metody init (która jest rzeczywistym wywołaniem konstruktora zdefiniowanego przez użytkownika). Ten dwuetapowy proces umożliwia utworzenie rzeczywistej instancji przed wywołaniem metody konstruktora klasy, która jest po prostu inną metodą tej instancji.Teraz, w przypadku Pythona,
__new__
jest dodatkową funkcją, która jest dostępna dla użytkownika. Java nie zapewnia tej elastyczności ze względu na swój typowy charakter. Jeśli język udostępnia tę funkcję, wówczas implementator__new__
może zrobić wiele rzeczy w tej metodzie przed zwróceniem instancji, w tym w niektórych przypadkach utworzyć całkowicie nową instancję niezwiązanego obiektu. Takie podejście sprawdza się również w przypadku niezmiennych typów w przypadku Pythona.źródło
Myślę, że przydałaby się analogia C ++:
__new__
po prostu przydziela pamięć dla obiektu. Zmienne instancji obiektu potrzebują pamięci, aby je utrzymać, i tak właśnie__new__
zrobiłby ten krok .__init__
zainicjuj wewnętrzne zmienne obiektu do określonych wartości (może być domyślna).źródło
Wywoływany
__init__
jest po__new__
, aby po zastąpieniu go w podklasie dodany kod był nadal wywoływany.Jeśli próbujesz podklasować klasę, która już ma klasę
__new__
, ktoś nieświadomy tego może zacząć od dostosowania__init__
i przekierowania wywołania do podklasy__init__
. Ta konwencja wywoływania__init__
po__new__
pomaga działać zgodnie z oczekiwaniami.__init__
Nadal musi umożliwić żadnych parametrów nadklasą__new__
potrzebne, ale nie robiąc tak zazwyczaj tworzyć wyraźny błąd wykonania. I__new__
prawdopodobnie powinien wyraźnie zezwalać na*args
„** kw”, aby wyjaśnić, że rozszerzenie jest OK.Zasadniczo jest to zła forma posiadania obu
__new__
i__init__
tej samej klasy na tym samym poziomie dziedziczenia, ze względu na zachowanie opisane w oryginalnym plakacie.źródło
Niewielki powód poza tym, że po prostu tak się dzieje.
__new__
nie ma obowiązku inicjowania klasy, robi to inna metoda (__call__
być może - nie wiem na pewno).Nie
__init__
możesz nic zrobić, jeśli jest on już zainicjowany, lub możesz napisać nową metaklasę z nową,__call__
która wywołuje tylko__init__
nowe wystąpienia, a w przeciwnym razie po prostu zwraca__new__(...)
.źródło
Prostym powodem jest to, że nowy służy do tworzenia instancji, podczas gdy init służy do inicjowania instancji. Przed zainicjowaniem należy najpierw utworzyć instancję. Dlatego nowy powinien zostać wywołany przed init .
źródło
Teraz mam ten sam problem iz kilku powodów postanowiłem unikać dekoratorów, fabryk i metaklas. Zrobiłem tak:
Główny plik
Przykładowe klasy
W użyciu
Wypróbuj online!
źródło