Pytanie, które zamierzam zadać, wydaje się być duplikatem użycia przez Pythona funkcji __new__ i __init__? , ale niezależnie od tego, nadal nie jest dla mnie jasne, jaka jest praktyczna różnica między __new__
i __init__
.
Zanim spieszysz się, aby mi powiedzieć, że __new__
służy do tworzenia obiektów i __init__
inicjowania obiektów, pozwól mi wyjaśnić: rozumiem. W rzeczywistości to rozróżnienie jest dla mnie całkiem naturalne, ponieważ mam doświadczenie w C ++, gdzie mamy umieszczenie new , które podobnie oddziela alokację obiektów od inicjalizacji.
Samouczek API Python C wyjaśnia to tak:
Nowy członek jest odpowiedzialny za tworzenie (a nie inicjowanie) obiektów tego typu. Jest ujawniany w Pythonie jako
__new__()
metoda. ... Jednym z powodów wdrożenia nowej metody jest zapewnienie początkowych wartości zmiennych instancji .
Więc tak - rozumiem, co __new__
robi, ale mimo to nadal nie rozumiem, dlaczego jest to przydatne w Pythonie. Podany przykład mówi, że __new__
może to być przydatne, jeśli chcesz „zapewnić początkowe wartości zmiennych instancji”. Cóż, czy to nie jest dokładnie to, co __init__
będzie?
W samouczku interfejsu API języka C pokazano przykład tworzenia nowego typu (zwanego „Noddy”) i __new__
definiowania funkcji Type . Typ Noddy zawiera element członkowski typu string o nazwie first
, a ten element członkowski ciągu jest inicjowany do pustego ciągu, jak poniżej:
static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
.....
self->first = PyString_FromString("");
if (self->first == NULL)
{
Py_DECREF(self);
return NULL;
}
.....
}
Zauważ, że bez __new__
zdefiniowanej tutaj metody musielibyśmy użyć PyType_GenericNew
, która po prostu inicjalizuje wszystkie składowe zmiennej instancji na NULL. Zatem jedyną zaletą tej __new__
metody jest to, że zmienna instancji będzie zaczynać się jako pusty ciąg, w przeciwieństwie do NULL. Ale dlaczego jest to kiedykolwiek przydatne, skoro jeśli zależy nam na upewnieniu się, że zmienne instancji są zainicjowane do jakiejś domyślnej wartości, moglibyśmy to zrobić w __init__
metodzie?
źródło
__new__
nie jest boilerplate, ponieważ boilerplate nigdy się nie zmienia. Czasami trzeba zamienić ten konkretny kod na coś innego.super
wywołania) i zwracanie instancji są niezbędnymi częściami każdej__new__
implementacji, a także „schematem wzorcowym”, o którym mówię. Z drugiej stronypass
jest to poprawna implementacja dla__init__
- nie ma żadnego wymaganego zachowania.Prawdopodobnie istnieją inne zastosowania,
__new__
ale jest jedno naprawdę oczywiste: nie można podklasować niezmiennego typu bez użycia__new__
. Załóżmy na przykład, że chcesz utworzyć podklasę krotki, która może zawierać tylko wartości całkowite z przedziału od 0 dosize
.Po prostu nie możesz tego zrobić
__init__
- jeśli próbowałeś zmodyfikowaćself
w__init__
, interpreter narzekałby, że próbujesz zmodyfikować niezmienny obiekt.źródło
__new__
. Przechodzimycls
wyraźnie do__new__
ponieważ, jak możesz przeczytać tutaj__new__
zawsze wymaga typu jako pierwszego argumentu. Następnie zwraca instancję tego typu. Więc nie zwracamy instancji superklasy - zwracamy instancję klasycls
. W tym przypadku jest tak samo, jak powiedzieliśmytuple.__new__(ModularTuple, tup)
.__new__()
może zwracać obiekty typu innego niż klasa, z którą jest powiązany.__init__()
tylko inicjuje istniejące wystąpienie klasy.źródło
__init__
metody, które zawierają kod, który wygląda jakself.__class__ = type(...)
. To powoduje, że obiekt należy do innej klasy niż ten, który myślałeś, że tworzysz. Właściwie nie mogę go zmienić naint
tak, jak ty ... Otrzymuję błąd dotyczący typów sterty lub czegoś podobnego ... ale mój przykład przypisania go do dynamicznie tworzonej klasy działa.__init__()
jest wezwany. Na przykład, w odpowiedzi lonetwin , zostaniesz wywołanyTriangle.__init__()
lubSquare.__init__()
automatycznie, w zależności od tego, który typ__new__()
zwróci. Z tego, co powiedziałeś w swojej odpowiedzi (a czytałem to gdzie indziej), wydaje się, że żaden z nich nie powinien być, ponieważShape.__new__()
nie zwraca wystąpieniacls
(ani jednej z jego podklas).__init__()
Metody w odpowiedzi lonetwin są wywoływane, gdy tworzona jest instancja poszczególnych obiektów (tj. gdy ich__new__()
metoda powraca), a nie gdyShape.__new__()
zwraca.Shape.__init__()
(gdyby miał) nie zostałaby wezwana. Teraz wszystko ma więcej sensu ...:¬)
Nie jest to pełna odpowiedź, ale może coś, co ilustruje różnicę.
__new__
będzie zawsze wywoływana, gdy trzeba będzie utworzyć obiekt. Są sytuacje, w których__init__
nie zostaniesz wezwany. Jednym z przykładów jest to, że gdy usuwasz obiekty z pliku pikle, zostaną one przydzielone (__new__
), ale nie zainicjalizowane (__init__
).źródło
__new__
metody jest utworzenie (to implikuje alokację pamięci) instancji klasy i zwrócenie jej. Inicjalizacja jest oddzielnym krokiem i jest zwykle widoczna dla użytkownika. Jeśli masz konkretny problem, zadaj osobne pytanie.Chcę tylko dodać słowo o zamiarze (w przeciwieństwie do zachowania) definiowania
__new__
kontra__init__
.Na to pytanie natknąłem się (między innymi), gdy próbowałem zrozumieć najlepszy sposób zdefiniowania fabryki klas. Zdałem sobie sprawę, że jednym ze sposobów, w jaki
__new__
koncepcyjnie różni się od tego,__init__
jest fakt, że korzyść__new__
jest dokładnie tym, co zostało stwierdzone w pytaniu:Biorąc pod uwagę podany scenariusz, dbamy o początkowe wartości zmiennych instancji, gdy instancja jest w rzeczywistości samą klasą. Tak więc, jeśli dynamicznie tworzymy obiekt klasy w czasie wykonywania i musimy zdefiniować / kontrolować coś specjalnego w kolejnych tworzonych instancjach tej klasy, zdefiniowalibyśmy te warunki / właściwości w
__new__
metodzie metaklasy.Byłem zdezorientowany, dopóki nie pomyślałem o zastosowaniu tego pojęcia, a nie tylko o jego znaczeniu. Oto przykład, który, miejmy nadzieję, wyjaśni różnicę:
Zauważ, że to tylko demonstracyjny przykład początkowy. Istnieje wiele sposobów na uzyskanie rozwiązania bez uciekania się do podejścia opartego na fabryce klas, takiego jak powyżej, a nawet jeśli zdecydujemy się wdrożyć rozwiązanie w ten sposób, ze względu na zwięzłość pominięto pewne zastrzeżenia (na przykład jawne zadeklarowanie metaklasy )
Jeśli tworzysz klasę zwykłą (inną niż metaklasa), to
__new__
nie ma to sensu, chyba że jest to przypadek specjalny, taki jak scenariusz zmienny kontra niezmienny w odpowiedzi ncoghlana (który jest zasadniczo bardziej szczegółowym przykładem pojęcia definiowania początkowe wartości / właściwości klasy / typu tworzonego za pośrednictwem, które__new__
mają być następnie zainicjowane za pośrednictwem__init__
).źródło