Dlaczego init () jest zawsze wywoływany po new ()?

Próbuję tylko usprawnić jedną z moich klas i wprowadzić pewne funkcjonalności w tym samym stylu, co flyweight design pattern .

Jednak jestem trochę zdezorientowany, dlaczego {[2] } jest zawsze wywoływany po __new__. Nie spodziewałem się tego. Czy ktoś może mi powiedzieć dlaczego tak się dzieje i jak mogę inaczej zaimplementować tę funkcjonalność? (Oprócz wprowadzenia implementacji do __new__, która wydaje się dość chwiejna.)

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?
Author: martineau, 2009-03-23

17 answers

Użyj __new _ _ kiedy musisz kontrolować utworzenie nowej instancji. Użycie __init _ _ gdy musisz kontrolować inicjalizację nowej instancji.

__Nowy_ _ jest pierwszym krokiem tworzenia instancji. Nazywa się pierwszy i jest odpowiedzialny za zwrot nowego przykład twojej klasy. In contrast, _ _ init _ _ niczego nie zwraca; odpowiada tylko za inicjalizację instancja po jej utworzeniu.

W Generale, nie powinien pan tego robić. override _ _ new _ _ unless you ' re podklasowanie typu niezmiennego jak str, int, unicode lub tuple.

From: http://mail.python.org/pipermail/tutor/2008-April/061426.html

Powinieneś wziąć pod uwagę, że to, co próbujesz zrobić, zwykle odbywa się za pomocą fabryki i to jest najlepszy sposób, aby to zrobić. Używanie __new _ _ nie jest dobrym czystym rozwiązaniem, więc proszę rozważyć użycie fabryki. Tutaj masz a dobry przykład fabryczny .

 479
Author: mpeterson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-23 17:31:09

__new__ jest metodą klasy statycznej, podczas gdy __init__ is instance method. __new__ musi najpierw utworzyć instancję, więc __init__ można go zainicjować. Zauważ, że __init__ bierze self jako parametr. Dopóki nie utworzysz instancji nie ma self.

Domyślam się, że próbujesz zaimplementować singleton patternw Pythonie. Jest na to kilka sposobów.

Również, począwszy od Pythona 2.6, możesz używać klasy dekoratorzy .

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

@singleton
class MyClass:
  ...
 149
Author: vartec,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-22 15:39:08

W większości znanych języków OO, wyrażenie takie jak SomeClass(arg1, arg2) przydziela nową instancję, inicjalizuje jej atrybuty, a następnie zwraca ją.

W większości znanych języków oo, część" initialise the instance 's attributes" może być dostosowana dla każdej klasy poprzez zdefiniowanie konstruktora , który jest w zasadzie tylko blokiem kodu, który działa na nowej instancji (używając argumentów dostarczonych do wyrażenia konstruktora), aby skonfigurować dowolne warunki początkowe. pożądane. W Pythonie odpowiada to metodzie klasy " __init__.

__new__ w Pythonie jest niczym więcej i niczym mniejszym niż podobnym dostosowaniem dla poszczególnych klas części "allocate a new instance". Pozwala to oczywiście na wykonywanie nietypowych czynności, takich jak zwracanie istniejącej instancji zamiast przydzielania nowej. Tak więc w Pythonie nie powinniśmy myśleć o tej części jako koniecznie obejmującej alokację; wszystko, czego wymagamy, to to, że __new__ pojawia się odpowiednia instancja z gdzieś.

Ale to wciąż tylko połowa zadania i nie ma sposobu, aby system Pythona wiedział, że czasami chcesz uruchomić drugą połowę zadania (__init__) później, a czasami nie. jeśli chcesz tego zachowania, musisz to wyraźnie powiedzieć.

Często można refaktorować, więc wystarczy __new__, albo nie trzeba __new__, albo tak, że __init__ zachowuje się inaczej na już zainicjowanym obiekcie. Ale jeśli naprawdę chcesz, Python naprawdę pozwala na redefinicję "zadanie", tak że SomeClass(arg1, arg2) niekoniecznie wywołuje __new__, po którym następuje __init__. Aby to zrobić, musisz utworzyć metaklasę i zdefiniować jej metodę __call__.

Metaklasa jest tylko klasą klasy. Metoda class ' __call__ kontroluje, co się dzieje, gdy wywołujesz instancje klasy. Więc metaklass' __call__ metoda kontroluje, co się dzieje, gdy wywołujesz klasę; tzn. pozwala przedefiniować mechanizm tworzenia instancji od początku do końca. Jest to poziom na które można najbardziej elegancko zaimplementować całkowicie niestandardowy proces tworzenia instancji, taki jak wzór Singletona. W rzeczywistości, mając mniej niż 10 linii kodu, możesz zaimplementować metaklasę Singleton, która nie wymaga nawet od Ciebie futz __new__ w ogóle , i może przekształcić dowolną inaczej-normalną klasę w singleton, dodając po prostu __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

Jednak jest to prawdopodobnie głębsza Magia, niż jest to naprawdę uzasadnione w tej sytuacji!

 126
Author: Ben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-29 07:39:47

Cytuję dokumentację:

Typowe implementacje tworzą nową instancję klasy poprzez wywołanie metoda superclass__ new _ _ () przy użyciu "super (currentclass, cls).__ new _ _ (cls[, ...]) "z odpowiednimi argumentami, a następnie modyfikowanie nowo utworzonej instancji w razie potrzeby przed jej zwróceniem.

...

Jeśli _ _ new _ _ () nie zwraca instancji cls, to nowa metoda __INIT__ () instancji nie zostanie wywołana.

__ new _ _ () ma na celu przede wszystkim umożliwienie podklas immutable typy (takie jak int, str lub tuple) do dostosowywania tworzenia instancji.

 20
Author: tgray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-28 13:07:59

Zdaję sobie sprawę, że to pytanie jest dość stare, ale miałem podobny problem. Oto 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

I used this page as a resource http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html

 10
Author: Tobias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-13 14:31:49

Myślę, że prosta odpowiedź na to pytanie jest taka, że jeśli __new__ zwróci wartość tego samego typu co Klasa, to funkcja __init__ zostanie wykonana, w przeciwnym razie nie.A._dict('key') która jest tą samą klasą co cls, więc __init__ zostanie wykonana.

 8
Author: PMN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-07 14:45:17

Gdy __new__ zwraca instancję tej samej klasy, __init__ jest następnie uruchamiane na zwracanym obiekcie. Nie można użyć __new__, aby zapobiec uruchomieniu __init__. Nawet jeśli zwrócisz wcześniej utworzony obiekt z __new__, będzie on podwójny (potrójny, itd...) inicjowane przez __init__ raz po raz.

Oto ogólne podejście do wzoru Singletona, który 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 .

[[11]}inne podejście, które w rzeczywistości wymaga {[2] } jest użycie classmethods:
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"

Zwróć uwagę, że przy takim podejściu musisz ozdobić wszystkie swoje metody @classmethod, ponieważ nigdy nie użyjesz żadnej prawdziwej instancji MyClass.

 8
Author: Zaar Hai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-01 10:17:54
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._dict właściwość automatycznie staje się dostępna z A jako A._dict, więc uważaj, aby nie nadpisać jej przypadkowo.

 5
Author: Antony Hatchkins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-30 12:37:18

_ _ new _ _ powinien zwracać nową, pustą instancję klasy. __init _ _ jest następnie wywoływany do inicjalizacji tej instancji. Nie dzwonisz do _ _ init _ _ w "Nowym" przypadku _ _ new__, więc jest ono wywoływane dla Ciebie. Kod wywołujący __new__ nie śledzi, czy __init__ został wywołany na 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ł zainicjowany. Sprawdź istnienie tego atrybutu jako pierwszej rzeczy w __init__ i nie postępuj dalej, jeśli tak było.

 4
Author: Andrew Wilkinson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-23 17:26:35

Aktualizacja do odpowiedzi @ AntonyHatchkins, prawdopodobnie potrzebujesz osobnego słownika instancji dla każdej klasy metatype, co oznacza, że powinieneś mieć metodę __init__ w metaklasie, aby zainicjować obiekt klasy tym słownikiem 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()

Przeszedłem do przodu i zaktualizowałem oryginalny kod metodą __init__ i zmieniłem składnię na notację Pythona 3 (NO-ARG call to super i metaclass w argumentach klasy zamiast jako atrybut).

Tak czy inaczej, ważne jest to, że twoja metoda inicjalizacji klasy (__call__) nie uruchomi ani __new__ Ani __init__, Jeśli klucz zostanie znaleziony. Jest to znacznie czystsze niż użycie __new__, co wymaga oznaczenia obiektu, jeśli chcesz pominąć domyślny Krok __init__.

 4
Author: Mad Physicist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-18 19:39:06

Odwołując się do tego doc :

Podczas podklasowania niezmiennych typów wbudowanych, takich jak Liczby i ciągi znaków, i czasami w innych sytuacjach, statyczna metoda new przychodzi w poręcznym. new jest pierwszym krokiem w budowie instancji, wywołanym przed init .

Nowa metoda jest wywoływana z klasą jako jej pierwszy argument; jego obowiązkiem jest zwrócenie nowej instancji tego klasy.

Porównaj to z init: init jest wywoływany z instancją jako pierwszy argument i nic nie zwraca; jego odpowiedzialność polega na zainicjowaniu instancji.

Są sytuacje gdzie nowa instancja jest tworzona bez wywołania init (na przykład gdy instancja jest ładowana z ogórka). Nie ma sposobu na stworzenie nowa instancja bez wywołania new (chociaż w niektórych przypadkach można unikaj wywoływania klas bazowych Nowy ).

Jeśli chodzi o to, co chcesz osiągnąć, istnieje również w tym samym dokumencie informacje o Singleton pattern

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:
...
 3
Author: octoback,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-30 06:13:28

[[14]} kopanie trochę głębiej w to!

Typ klasy generycznej w Cpythonie to type, a jej klasą bazową jest Object (chyba że wyraźnie zdefiniujesz inną klasę bazową, taką jak metaklasa). Sekwencję wywołań niskiego poziomu można znaleźć tutaj . Pierwszą wywołaną metodą jest type_call, która następnie wywołuje tp_new, a następnie tp_init.

Interesujące jest to, że tp_new wywoła Object ' s (base class) nową metodę object_new, która wykonuje tp_alloc (PyType_GenericAlloc) który przydziela pamięć dla obiektu:)

W tym momencie obiekt zostanie wytworzony w pamięci i zostanie wywołana metoda __init__. Jeśli {[10] } nie jest zaimplementowany w twojej klasie, to object_init zostanie wywołany i nic nie zrobi:)

Następnie type_call po prostu zwraca obiekt, który wiąże się z Twoją zmienną.

 3
Author: xpapazaf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-15 20:47:55

Należy spojrzeć na __init__ jako prosty konstruktor w tradycyjnych językach OO. Na przykład, jeśli jesteś zaznajomiony z Javą lub C++, konstruktor jest przekazywany wskaźnik do własnej instancji niejawnie. W przypadku Javy jest to zmienna this. Gdyby ktoś sprawdzał kod bajtowy wygenerowany dla Javy, zauważałby dwa wywołania. Pierwszym wywołaniem jest metoda "nowa", a następnym wywołaniem jest metoda init (która jest rzeczywistym wywołaniem konstruktora zdefiniowanego przez użytkownika). Ten dwuetapowy proces umożliwia wytworzenie rzeczywistej instancji przed wywołaniem metody konstruktora klasy, która jest tylko inną metodą tej instancji.

Teraz, w przypadku Pythona, {[2] } jest dodanym obiektem dostępnym dla użytkownika. Java nie zapewnia takiej elastyczności ze względu na swój typowy charakter. Jeśli język dostarczył tę funkcję, implementator __new__ może wykonać wiele czynności w tej metodzie przed zwróceniem instancji, w tym utworzyć zupełnie nową instancję niepowiązanego obiektu w niektórych przypadkach. I, to podejście działa również dobrze dla szczególnie dla niezmiennych typów w przypadku Pythona.

 3
Author: Guru Devanla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-25 09:32:19

{[0] } jest wywoływana po __new__ tak, że gdy nadpisujesz go w podklasie, Twój dodany kod będzie nadal wywoływany.

Jeśli próbujesz podklasować klasę, która ma już __new__, ktoś nieświadomy tego może zacząć od dostosowania __init__ i przekierowania wywołania do podklasy __init__. Ta konwencja wywołania __init__ po __new__ pomaga to działać zgodnie z oczekiwaniami.

__init__ nadal musi zezwalać na dowolne parametry superclass __new__ potrzebne, ale nie robi tego zazwyczaj tworzy wyraźny błąd runtime. I {[1] } powinny prawdopodobnie wyraźnie zezwalać na *args I '* * kw', aby było jasne, że rozszerzenie jest OK.

Jest ogólnie złą formą mieć zarówno __new__, jak i __init__ w tej samej klasie na tym samym poziomie dziedziczenia, ze względu na zachowanie, które opisywał oryginalny plakat.

 1
Author: Jay Obermark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-18 16:30:45

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

Nie ma innego powodu niż to, że tak się robi. __new__ nie ponosi odpowiedzialności za inicjowanie klasy, robi to jakaś 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 implementuję tę funkcjonalność? (oprócz wprowadzenia implementacji do __new__ co wydaje się dość trudne).

Możesz mieć __init__ nic nie robić, jeśli została już zainicjowana, lub możesz napisać nową metaklasę z nową __call__, która wywołuje __init__ tylko w nowych instancjach, a w przeciwnym razie po prostu zwraca __new__(...).

 0
Author: Devin Jeanpierre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-23 17:23:37

Prosty powód jest taki, że new jest używany do tworzenia instancji, podczas gdy init jest używany do inicjalizacji instancji. Przed inicjalizacją instancja powinna być utworzona jako pierwsza. Dlatego nowypowinien być wywoływany przed init.

 0
Author: ZQ Hu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-23 13:48:43

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

Plik Główny

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>
  • Warning: nie powinieneś inicjować rodzica, to zderzy się z innymi klasami-chyba że zdefiniowałeś osobny cache w każdym z dzieci, to nie jest to, czego chcemy.
  • Warning: it seems a class with Parent as dziadek zachowuje się dziwnie. [Niezweryfikowany]

Wypróbuj online!

 0
Author: Soaku,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-29 20:40:37