Pourquoi __init __ () est-il toujours appelé après __new __ ()?

568

J'essaie simplement de rationaliser l'une de mes classes et j'ai introduit certaines fonctionnalités dans le même style que le modèle de conception de poids mouche .

Cependant, je suis un peu confus quant à la raison pour laquelle __init__on appelle toujours après __new__. Je ne m'attendais pas à ça. Quelqu'un peut-il me dire pourquoi cela se produit et comment je peux implémenter cette fonctionnalité autrement? (En plus de mettre l'implémentation dans le __new__qui semble assez hacky.)

Voici un exemple:

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

Les sorties:

NEW
INIT

EXISTS
INIT

EXISTS
INIT

Pourquoi?

Dan
la source
essayait également de comprendre le modèle de conception, et a entendu parler pour la première fois de: modèle de conception de mouche .. et très bon lien ayant un exemple dans presque toutes les langues populaires.
EmmaYang

Réponses:

598

À utiliser __new__lorsque vous devez contrôler la création d'une nouvelle instance.

À utiliser __init__lorsque vous devez contrôler l'initialisation d'une nouvelle instance.

__new__est la première étape de la création d'une instance. Il est appelé en premier et est responsable du retour d'une nouvelle instance de votre classe.

En revanche, __init__ne renvoie rien; il est uniquement responsable de l'initialisation de l'instance après sa création.

En général, vous ne devriez pas avoir besoin de remplacer à __new__moins que vous ne sous-classiez un type immuable comme str, int, unicode ou tuple.

Depuis avril 2008 post: Quand utiliser __new__vs __init__? sur mail.python.org.

Vous devriez considérer que ce que vous essayez de faire se fait généralement avec une usine et c'est la meilleure façon de le faire. L'utilisation __new__n'est pas une bonne solution propre, veuillez donc considérer l'utilisation d'une usine. Voici un bon exemple d'usine .

mpeterson
la source
1
J'ai fini par utiliser __new__une classe Factory, qui est devenue assez propre, alors merci pour votre contribution.
Dan
11
Désolé, je ne suis pas d'accord pour que l'utilisation de __new__soit strictement limitée aux cas indiqués. Je l'ai trouvé très utile pour implémenter des usines de classes génériques extensibles - voir ma réponse à la question Utilisation incorrecte de __new__pour générer des classes en Python? pour un exemple de le faire.
martineau
171

__new__est la méthode de classe statique, tandis que la __init__méthode d'instance. __new__doit d'abord créer l'instance, donc __init__peut l'initialiser. Notez que __init__prend selfcomme paramètre. Tant que vous ne créez pas d'instance, il n'y en a pas self.

Maintenant, je suppose que vous essayez d'implémenter un modèle singleton en Python. Il y a plusieurs façons de procéder.

De plus, à partir de Python 2.6, vous pouvez utiliser des décorateurs de classe .

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

@singleton
class MyClass:
  ...
vartec
la source
8
@Tyler Long, je ne comprends pas très bien comment fonctionne "@singleton"? Parce que le décorateur renvoie une fonction mais MyClass est une classe.
Alcott
11
Pourquoi le dictionnaire? Étant donné que cls sera toujours le même et que vous obtenez un nouveau dictionnaire, par exemple, pour chaque singleton, vous créez un dictionnaire avec un seul élément.
Winston Ewert
7
@Alcott: Aucune opinion nécessaire - la documentation est d' accord avec vous.
Ethan Furman
2
@Alcott. oui le décorateur retourne une fonction. mais la classe et la fonction sont appelables. Je pense que instances = {} devrait être une variable globale.
Tyler Long
4
@TylerLong Alcott a un bon point. Il en résulte que le nom MyClassest lié à une fonction qui renvoie des instances de la classe d'origine. Mais il n'y a plus aucun moyen de faire référence à la classe d'origine, et le fait d' MyClassêtre une fonction interrompt isinstance/ issubclassvérifie, l'accès aux attributs / méthodes de classe directement MyClass.something, nommer la classe dans les superappels, etc., etc. Que ce soit un problème ou non dépend de la classe à laquelle vous l'appliquez (et le reste du programme).
Ben
149

Dans la plupart des langages OO bien connus, une expression comme SomeClass(arg1, arg2)va allouer une nouvelle instance, initialiser les attributs de l'instance, puis la renvoyer.

Dans la plupart des langages OO bien connus, la partie "initialiser les attributs de l'instance" peut être personnalisée pour chaque classe en définissant un constructeur , qui est fondamentalement juste un bloc de code qui opère sur la nouvelle instance (en utilisant les arguments fournis à l'expression constructeur ) pour mettre en place les conditions initiales souhaitées. En Python, cela correspond à la __init__méthode de la classe .

Python __new__n'est rien de plus et rien de moins que la personnalisation par classe similaire de la partie "allouer une nouvelle instance". Bien sûr, cela vous permet de faire des choses inhabituelles telles que retourner une instance existante plutôt que d'en allouer une nouvelle. Donc, en Python, nous ne devrions pas vraiment penser à cette partie comme impliquant nécessairement l'allocation; tout ce dont nous avons besoin, c'est de __new__trouver une instance appropriée quelque part.

Mais ce n'est encore que la moitié du travail, et il n'y a aucun moyen pour le système Python de savoir que parfois vous voulez exécuter l'autre moitié du travail ( __init__) par la suite et parfois non. Si vous voulez ce comportement, vous devez le dire explicitement.

Souvent, vous pouvez refactoriser de sorte que vous n'ayez besoin que __new__, ou que vous n'en ayez pas besoin __new__, ou que cela __init__se comporte différemment sur un objet déjà initialisé. Mais si vous le voulez vraiment, Python vous permet en fait de redéfinir "le travail", donc cela SomeClass(arg1, arg2)n'appelle pas nécessairement __new__suivi de __init__. Pour ce faire, vous devez créer une métaclasse et définir sa __call__méthode.

Une métaclasse n'est que la classe d'une classe. Et une __call__méthode de classe contrôle ce qui se passe lorsque vous appelez des instances de la classe. Ainsi , une métaclasse « __call__contrôles de méthode ce qui se produit lorsque vous appelez une classe; c'est-à-dire qu'il vous permet de redéfinir le mécanisme de création d'instance du début à la fin . Il s'agit du niveau auquel vous pouvez implémenter le plus élégamment un processus de création d'instance complètement non standard tel que le modèle singleton. En fait, avec moins de 10 lignes de code que vous pouvez mettre en œuvre une Singletonmétaclasse qui alors ne même pas vous obliger à Futz avec __new__ du tout , et peut transformer une classe autrement normale dans un singleton en ajoutant simplement __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

Cependant, c'est probablement une magie plus profonde que ce qui est vraiment justifié dans cette situation!

Ben
la source
25

Pour citer la documentation :

Les implémentations typiques créent une nouvelle instance de la classe en invoquant la méthode __new __ () de la superclasse en utilisant "super (currentclass, cls) .__ new __ (cls [, ...])" avec les arguments appropriés, puis en modifiant l'instance nouvellement créée si nécessaire avant de le retourner.

...

Si __new __ () ne renvoie pas une instance de cls, la méthode __init __ () de la nouvelle instance ne sera pas invoquée.

__new __ () est principalement destiné à permettre aux sous-classes de types immuables (comme int, str ou tuple) de personnaliser la création d'instance.

tgray
la source
18
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.Voilà un point important. Si vous renvoyez une instance différente, original __init__n'est jamais appelé.
Jeffrey Jose
@tgray Je me rends compte que votre réponse vient des docs, mais je suis curieux de savoir si vous connaissez un cas d'utilisation de ne pas retourner une instance de cls. Il semble que lorsque l'objet retourné de __new__est vérifié pour sa classe, la méthode lèverait une erreur plutôt que de laisser la vérification échouée passer silencieusement, car je ne comprends pas pourquoi vous voudriez jamais renvoyer autre chose qu'un objet de la classe .
soporific312
@ soporific312 Je n'ai vu aucun cas d'utilisation. Cette réponse traite de certains des motifs de la conception, bien qu'ils n'aient également vu aucun code exploitant cette «fonctionnalité».
tgray
13

Je me rends compte que cette question est assez ancienne mais j'ai eu un problème similaire. Ce qui suit a fait ce que je voulais:

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

J'ai utilisé cette page comme ressource http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html

Alexey
la source
7
Remarque: __new__ renvoie toujours un objet approprié, donc __init__ est toujours appelé - même si l'instance existe déjà.
Oddthinking
10

Je pense que la réponse simple à cette question est que, si __new__renvoie une valeur qui est du même type que la classe, la __init__fonction s'exécute, sinon ce ne sera pas le cas. Dans ce cas, votre code retourne A._dict('key')qui est de la même classe que cls, donc __init__sera exécuté.

PMN
la source
10

Lorsque __new__renvoie l'instance de la même classe, __init__est exécutée ensuite sur l'objet retourné. C'est-à-dire que vous ne pouvez PAS utiliser __new__pour empêcher __init__d'être exécuté. Même si vous retournez un objet précédemment créé depuis __new__, il sera double (triple, etc ...) initialisé par __init__encore et encore.

Voici l'approche générique du modèle Singleton qui étend la réponse vartec ci-dessus et la corrige:

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

L'histoire complète est ici .

Une autre approche, qui consiste en __new__fait à utiliser des méthodes de classe:

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"

Veuillez faire attention, avec cette approche, vous devez décorer TOUTES vos méthodes @classmethod, car vous n'utiliserez jamais d'instance réelle de MyClass.

Zaar Hai
la source
6

Se référant à ce document :

Lors de la sous-classification de types intégrés immuables tels que des nombres et des chaînes, et parfois dans d'autres situations, la nouvelle méthode statique est très pratique. new est la première étape de la construction d'une instance, appelée avant init .

La nouvelle méthode est appelée avec la classe comme premier argument; sa responsabilité est de renvoyer une nouvelle instance de cette classe.

Comparez cela à init : init est appelé avec une instance comme premier argument, et il ne retourne rien; sa responsabilité est d'initialiser l'instance.

Il existe des situations où une nouvelle instance est créée sans appeler init (par exemple lorsque l'instance est chargée à partir d'un pickle). Il n'y a aucun moyen de créer une nouvelle instance sans appeler new (bien que dans certains cas, vous puissiez vous en sortir en appelant new à une classe de base ).

Concernant ce que vous souhaitez réaliser, il y a aussi dans le même doc des informations sur le motif 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

vous pouvez également utiliser cette implémentation de PEP 318, en utilisant un décorateur

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

@singleton
class MyClass:
...
kiriloff
la source
1
Appeler une forme bâtarde initde __new__sons vraiment hacky. C'est à cela que servent les métaclasses.
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')

les sorties:

NEW
INIT

NEW
INIT

EXISTS

NB En tant qu'effet secondaire, la M._dictpropriété devient automatiquement accessible à partir Ade A._dictce moment- là, veillez donc à ne pas l'écraser accidentellement.

Antony Hatchkins
la source
Il vous manque une __init__méthode qui définit cls._dict = {}. Vous ne voulez probablement pas un dictionnaire partagé par toutes les classes de ce métatype (mais +1 pour l'idée).
Mad Physicist
5

__new__ devrait renvoyer une nouvelle instance vierge d'une classe. __init__ est ensuite appelé pour initialiser cette instance. Vous n'appelez pas __init__ dans le "NOUVEAU" cas de __new__, donc il est appelé pour vous. Le code qui appelle __new__ne garde pas la trace de si __init__ a été appelé sur une instance particulière ou non, car il fait quelque chose de très inhabituel ici.

Vous pouvez ajouter un attribut à l'objet dans la fonction __init__ pour indiquer qu'il a été initialisé. Vérifiez l'existence de cet attribut comme première chose dans __init__ et ne continuez pas s'il l'a été.

Andrew Wilkinson
la source
5

Une mise à jour de la réponse @AntonyHatchkins, vous voulez probablement un dictionnaire séparé d'instances pour chaque classe du métatype, ce qui signifie que vous devriez avoir une __init__méthode dans la métaclasse pour initialiser votre objet classe avec ce dictionnaire au lieu de le rendre global dans toutes les classes.

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

J'ai continué et mis à jour le code d'origine avec une __init__méthode et changé la syntaxe en notation Python 3 (appel sans argument àsuper et métaclasse dans les arguments de classe au lieu d'un attribut).

Quoi qu'il en soit, le point important ici est que votre initialiseur de classe ( __call__méthode) ne s'exécutera pas non plus __new__ou __init__si la clé est trouvée. C'est beaucoup plus propre que l'utilisation __new__, ce qui vous oblige à marquer l'objet si vous souhaitez ignorer l' __init__étape par défaut .

Physicien fou
la source
4

Creuser un peu plus en profondeur!

Le type d'une classe générique dans CPython est typeet sa classe de base est Object(sauf si vous définissez explicitement une autre classe de base comme une métaclasse). La séquence des appels de bas niveau peut être trouvée ici . La première méthode appelée est celle type_callqui appelle tp_newensuite, puistp_init .

La partie intéressante ici est que tp_newva appeler la Objectnouvelle méthode de (classe de base) object_newqui fait un tp_alloc( PyType_GenericAlloc) qui alloue la mémoire pour l'objet :)

À ce stade, l'objet est créé en mémoire, puis la __init__méthode est appelée. Si __init__n'est pas implémenté dans votre classe, leobject_init obtient est appelé et ne fait rien :)

type_callRetourne ensuite simplement l'objet qui se lie à votre variable.

xpapazaf
la source
4

Il faut regarder __init__comme un simple constructeur dans les langages OO traditionnels. Par exemple, si vous connaissez Java ou C ++, le constructeur reçoit implicitement un pointeur vers sa propre instance. Dans le cas de Java, c'est la thisvariable. Si l'on devait inspecter le code d'octet généré pour Java, on remarquerait deux appels. Le premier appel est à une "nouvelle" méthode, puis l'appel suivant est à la méthode init (qui est l'appel réel au constructeur défini par l'utilisateur). Ce processus en deux étapes permet la création de l'instance réelle avant d'appeler la méthode constructeur de la classe qui n'est qu'une autre méthode de cette instance.

Maintenant, dans le cas de Python, __new__est une fonctionnalité supplémentaire accessible à l'utilisateur. Java n'offre pas cette flexibilité, en raison de sa nature typée. Si un langage fournissait cette fonctionnalité, l'implémenteur de __new__pourrait faire beaucoup de choses dans cette méthode avant de renvoyer l'instance, y compris la création d'une instance totalement nouvelle d'un objet non lié dans certains cas. Et, cette approche fonctionne également bien pour en particulier pour les types immuables dans le cas de Python.

grdvnl
la source
4

Cependant, je suis un peu confus quant à la raison pour laquelle __init__on appelle toujours après __new__.

Je pense que l'analogie C ++ serait utile ici:

  1. __new__alloue simplement de la mémoire à l'objet. Les variables d'instance d'un objet ont besoin de mémoire pour le contenir, et c'est ce que l'étape __new__ferait.

  2. __init__ initialiser les variables internes de l'objet à des valeurs spécifiques (peut être par défaut).

HAltos
la source
2

Le __init__est appelé après __new__pour que lorsque vous le remplacez dans une sous-classe, votre code ajouté sera toujours appelé.

Si vous essayez de sous-classer une classe qui a déjà un __new__, quelqu'un qui ne le sait pas peut commencer par adapter le __init__et transférer l'appel vers la sous-classe __init__. Cette convention d'appeler __init__après__new__ aide à fonctionner comme prévu.

Le __init__doit encore autoriser tous les paramètres dont la superclasse __new__avait besoin, mais à défaut, cela créera généralement une erreur d'exécution claire. Et le __new__devrait probablement autoriser explicitement *argset '** kw', pour indiquer clairement que l'extension est OK.

Il est généralement mauvais d'avoir à la fois __new__et __init__dans la même classe au même niveau d'héritage, en raison du comportement décrit par l'affiche originale.

Jay Obermark
la source
1

Cependant, je suis un peu confus quant à la raison pour laquelle __init__on appelle toujours après __new__.

Pas grand-chose d'autre que cela. __new__n'a pas la responsabilité d'initialiser la classe, une autre méthode le fait ( __call__, peut-être - je ne suis pas sûr).

Je ne m'attendais pas à ça. Quelqu'un peut-il me dire pourquoi cela se produit et comment j'implémenter cette fonctionnalité autrement? (à part mettre l'implémentation dans le __new__qui semble assez hacky).

Vous __init__n'auriez rien pu faire si elle avait déjà été initialisée, ou vous pouviez écrire une nouvelle métaclasse avec une nouvelle __call__qui n'appelle que __init__de nouvelles instances, et sinon elle revient simplement __new__(...).

Devin Jeanpierre
la source
1

La raison simple est que le nouveau est utilisé pour créer une instance, tandis que init est utilisé pour initialiser l'instance. Avant l'initialisation, l'instance doit être créée en premier. C'est pourquoi new devrait être appelé avant init .

ZQ Hu
la source
1

Maintenant, j'ai le même problème, et pour certaines raisons, j'ai décidé d'éviter les décorateurs, les usines et les métaclasses. Je l'ai fait comme ça:

Fichier principal

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

Exemples de classes

class A(Parent):

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


class B(Parent):

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

Utilisé

>>> 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>
  • Avertissement: Vous ne devriez pas initialiser Parent, il sera collision avec d'autres classes - sauf si vous avez défini un cache séparé dans chacun des enfants, ce n'est pas ce que nous voulons.
  • Attention: il semble qu'une classe avec Parent car ses grands-parents se comporte bizarrement. [Non vérifié]

Essayez-le en ligne!

Trèfle rouge
la source