Dlaczego funkcja __init __ () jest zawsze wywoływana po __new __ ()?


568

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?


próbowałem również zrozumieć wzór projektowy, a po raz pierwszy usłyszałem o: wzornictwie wzorowanym na lekkości i bardzo dobrym łączu, mającym przykład w prawie wszystkich popularnych językach.
EmmaYang

Odpowiedzi:


598

Użyj, __new__gdy chcesz kontrolować tworzenie nowej instancji.

Użyj, __init__gdy potrzebujesz kontrolować inicjowanie nowej instancji.

__new__jest pierwszym krokiem tworzenia instancji. Nazywa się pierwszy i jest odpowiedzialny za zwrócenie nowej instancji twojej klasy.

Przeciwnie, __init__nic nie zwraca; odpowiada tylko za zainicjowanie instancji po jej utworzeniu.

Zasadniczo nie powinieneś zastępować, __new__chyba że podklasujesz niezmienny typ, taki jak str, int, Unicode lub krotka.

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 .


1
Skończyło się na użyciu __new__klasy Factory, która stała się dość czysta, więc dziękuję za twój wkład.
Dan

11
Niestety nie zgadzam się, że użycie __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.
martineau

170

__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 ma self.

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 .

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...

8
@Tyler Long, nie do końca rozumiem, jak działa „@singleton”? Ponieważ dekorator zwraca funkcję, ale MyClass jest klasą.
Alcott,

11
Dlaczego słownik? Ponieważ cls zawsze będzie taki sam i otrzymasz nowy słownik, na przykład dla każdego singletona, tworzysz słownik z tylko jednym elementem.
Winston Ewert

7
@Alcott: Nie potrzeba opinii - dokumenty zgadzają się z tobą.
Ethan Furman,

2
@Alcott. tak, dekorator zwraca funkcję. ale zarówno klasa, jak i funkcja są wywoływalne. Myślę, że instancje = {} powinny być zmienną globalną.
Tyler Long,

4
@TylerLong Alcott ma rację. Powoduje MyClassto powiązanie nazwy z funkcją, która zwraca instancje oryginalnej klasy. Ale teraz nie ma żadnego sposobu na odwołanie się do oryginalnej klasy, a MyClassbycie funkcją psuje isinstance/ issubclasssprawdza, dostęp do atrybutów / metod klasy bezpośrednio jako MyClass.something, nazwanie klasy w superwywołaniach itp. Itp. To, czy jest to problem, czy nie, zależy od klasa, do której aplikujesz (i resztę programu).
Ben

148

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ęc SomeClass(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ć Singletonmetaklasa ż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!

class Singleton(type):
    def __init__(self, *args, **kwargs):
        super(Singleton, self).__init__(*args, **kwargs)
        self.__instance = None
    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super(Singleton, self).__call__(*args, **kwargs)
        return self.__instance

Jest to jednak prawdopodobnie głębsza magia, niż jest to naprawdę uzasadnione w tej sytuacji!


25

Aby zacytować dokumentację :

Typowe implementacje tworzą nowe wystąpienie klasy, wywołując metodę __new __ () superklasy za pomocą „super (currentclass, cls) .__ nowe __ (cls [, ...])” z odpowiednimi argumentami, a następnie modyfikując nowo utworzone wystąpienie w razie potrzeby przed zwrotem.

...

Jeśli __new __ () nie zwraca instancji cls, wówczas metoda __init __ () nowej instancji nie zostanie wywołana.

__new __ () ma głównie na celu umożliwienie podklasom niezmiennych typów (takich jak int, str lub krotka) na dostosowanie tworzenia instancji.


18
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.
Jeffrey Jose,

@tgray Zdaję sobie sprawę, że twoja odpowiedź pochodzi z dokumentów, ale jestem ciekawa, czy znasz przypadek użycia, który nie zwraca instancji cls. Wygląda na to, że gdy __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 .
soporific312

@ soporific312 Nie widziałem żadnych przypadków użycia. Ta odpowiedź omawia niektóre z uzasadnień projektu, chociaż nie widzieli również żadnego kodu, który wykorzystuje tę „funkcję”.
tgray

12

Zdaję sobie sprawę, że to pytanie jest dość stare, ale miałem podobny problem. Następujące czynności zrobiły, co chciałem:

class Agent(object):
    _agents = dict()

    def __new__(cls, *p):
        number = p[0]
        if not number in cls._agents:
            cls._agents[number] = object.__new__(cls)
        return cls._agents[number]

    def __init__(self, number):
        self.number = number

    def __eq__(self, rhs):
        return self.number == rhs.number

Agent("a") is Agent("a") == True

Użyłem tej strony jako zasobu http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html


7
Uwaga: __new__ zawsze zwraca odpowiedni obiekt, więc __init__ jest zawsze wywoływany - nawet jeśli instancja już istnieje.
Dziwne,

10

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 zwraca A._dict('key')tę samą klasę cls, co __init__zostanie wykonany.


10

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ą:

def SingletonClass(cls):
    class Single(cls):
        __doc__ = cls.__doc__
        _initialized = False
        _instance = None

        def __new__(cls, *args, **kwargs):
            if not cls._instance:
                cls._instance = super(Single, cls).__new__(cls, *args, **kwargs)
            return cls._instance

        def __init__(self, *args, **kwargs):
            if self._initialized:
                return
            super(Single, self).__init__(*args, **kwargs)
            self.__class__._initialized = True  # Its crucial to set this variable on the class!
    return Single

Pełna historia jest tutaj .

Innym podejściem, które w rzeczywistości wymaga __new__użycia metod klas:

class Singleton(object):
    __initialized = False

    def __new__(cls, *args, **kwargs):
        if not cls.__initialized:
            cls.__init__(*args, **kwargs)
            cls.__initialized = True
        return cls


class MyClass(Singleton):
    @classmethod
    def __init__(cls, x, y):
        print "init is here"

    @classmethod
    def do(cls):
        print "doing stuff"

Proszę zwrócić uwagę, że przy takim podejściu musisz udekorować WSZYSTKIE swoje metody @classmethod, ponieważ nigdy nie użyjesz żadnego prawdziwego wystąpienia MyClass.


6

Odnosząc się do tego dokumentu :

Przy podzkluczaniu niezmiennych wbudowanych typów, takich jak liczby i łańcuchy, a czasami w innych sytuacjach, przydatna jest nowa metoda statyczna . nowy jest pierwszym krokiem w budowie instancji, wywołanym przed init .

Nowa metoda jest wywoływana z klasy jako pierwszy argument; jego zadaniem jest zwrócenie nowej instancji tej klasy.

Porównaj to z init : init jest wywoływany z instancją jako pierwszym argumentem i nic nie zwraca; jego zadaniem jest zainicjowanie instancji.

Są sytuacje, w których tworzona jest nowa instancja bez wywoływania init (na przykład, gdy instancja jest ładowana z marynaty). Nie ma sposobu na utworzenie nowej instancji bez wywołania new (chociaż w niektórych przypadkach można uniknąć wywoływania nowej klasy podstawowej ).

Jeśli chodzi o to, co chcesz osiągnąć, w tym samym dokumencie znajdują się również informacje o wzorze Singleton

class Singleton(object):
        def __new__(cls, *args, **kwds):
            it = cls.__dict__.get("__it__")
            if it is not None:
                return it
            cls.__it__ = it = object.__new__(cls)
            it.init(*args, **kwds)
            return it
        def init(self, *args, **kwds):
            pass

możesz również użyć tej implementacji z PEP 318, używając dekoratora

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
...

1
Nazywanie odurzonej formy initz __new__dźwięków naprawdę hacky. Po to są metaklasy.
Mad Physicist,

6
class M(type):
    _dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print 'EXISTS'
            return cls._dict[key]
        else:
            print 'NEW'
            instance = super(M, cls).__call__(key)
            cls._dict[key] = instance
            return instance

class A(object):
    __metaclass__ = M

    def __init__(self, key):
        print 'INIT'
        self.key = key
        print

a1 = A('aaa')
a2 = A('bbb')
a3 = A('aaa')

wyjścia:

NEW
INIT

NEW
INIT

EXISTS

NB jako efekt uboczny M._dictnieruchomość automatycznie staje się dostępny z Atak A._dictwięc zadbać, aby nie nadpisać go przypadkowo.


Brakuje ustawionej __init__metody cls._dict = {}. Prawdopodobnie nie chcesz, aby słownik był współużytkowany przez wszystkie klasy tego typu metaty (ale +1 za pomysł).
Mad Physicist,

5

__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.


5

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.

class MetaQuasiSingleton(type):
    def __init__(cls, name, bases, attibutes):
        cls._dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print('EXISTS')
            instance = cls._dict[key]
        else:
            print('NEW')
            instance = super().__call__(key)
            cls._dict[key] = instance
        return instance

class A(metaclass=MetaQuasiSingleton):
    def __init__(self, key):
        print 'INIT'
        self.key = key
        print()

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-arg superi 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.


4

Zagłębiam się trochę głębiej w to!

Typem ogólnej klasy w CPython jest typejej klasa podstawowa Object(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_callktóra następnie wywołuje, tp_newa następnie tp_init.

Interesujące jest to, że tp_newwywoła Objectnową metodę (klasy bazowej), object_newktó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_initzostaje wywołany i nic nie robi :)

Następnie type_callpo prostu zwraca obiekt, który wiąże się ze zmienną.


4

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 to thiszmienna. 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.


4

Jednak jestem trochę zdezorientowany, dlaczego __init__zawsze jest wywoływany po __new__.

Myślę, że przydałaby się analogia C ++:

  1. __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 .

  2. __init__ zainicjuj wewnętrzne zmienne obiektu do określonych wartości (może być domyślna).


2

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.


1

Jednak jestem trochę zdezorientowany, dlaczego __init__zawsze jest wywoływany po __new__.

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 spodziewałem się tego. Czy ktoś może mi powiedzieć, dlaczego tak się dzieje i jak w inny sposób zaimplementować tę funkcjonalność? (oprócz umieszczenia implementacji w tym, __new__co wydaje się dość hackerskie).

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__(...).


1

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 .


1

Teraz mam ten sam problem iz kilku powodów postanowiłem unikać dekoratorów, fabryk i metaklas. Zrobiłem tak:

Główny plik

def _alt(func):
    import functools
    @functools.wraps(func)
    def init(self, *p, **k):
        if hasattr(self, "parent_initialized"):
            return
        else:
            self.parent_initialized = True
            func(self, *p, **k)

    return init


class Parent:
    # Empty dictionary, shouldn't ever be filled with anything else
    parent_cache = {}

    def __new__(cls, n, *args, **kwargs):

        # Checks if object with this ID (n) has been created
        if n in cls.parent_cache:

            # It was, return it
            return cls.parent_cache[n]

        else:

            # Check if it was modified by this function
            if not hasattr(cls, "parent_modified"):
                # Add the attribute
                cls.parent_modified = True
                cls.parent_cache = {}

                # Apply it
                cls.__init__ = _alt(cls.__init__)

            # Get the instance
            obj = super().__new__(cls)

            # Push it to cache
            cls.parent_cache[n] = obj

            # Return it
            return obj

Przykładowe klasy

class A(Parent):

    def __init__(self, n):
        print("A.__init__", n)


class B(Parent):

    def __init__(self, n):
        print("B.__init__", n)

W użyciu

>>> A(1)
A.__init__ 1  # First A(1) initialized 
<__main__.A object at 0x000001A73A4A2E48>
>>> A(1)      # Returned previous A(1)
<__main__.A object at 0x000001A73A4A2E48>
>>> A(2)
A.__init__ 2  # First A(2) initialized
<__main__.A object at 0x000001A7395D9C88>
>>> B(2)
B.__init__ 2  # B class doesn't collide with A, thanks to separate cache
<__main__.B object at 0x000001A73951B080>
  • Ostrzeżenie: Należy nie initialize nadrzędna, to będzie kolidować z innymi klasami - o ile nie określono oddzielnej pamięci podręcznej w każdym z dziećmi, to nie to, co chcemy.
  • Ostrzeżenie: wygląda na to, że klasa z rodzicem jest dziadkiem, bo zachowuje się dziwnie. [Niesprawdzony]

Wypróbuj online!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.