web-dev-qa-db-fra.com

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

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

Cependant, je suis un peu confus quant à la raison pour laquelle __init__ est toujours appelé après __new__. Je ne m'y attendais pas. Quelqu'un peut-il me dire pourquoi cela se produit et comment je peux implémenter cette fonctionnalité autrement? (Mis à part mettre l'implémentation dans le __new__ qui se sent assez malin.)

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?

480
Dan

Utilisez _NEW_ lorsque vous devez contrôler la création d'une nouvelle instance. Utilisation _INIT_ lorsque vous devez contrôler l'initialisation d'une nouvelle instance.

_NEW_ est la première étape de la création d'instance. Il s'appelle d'abord, et est responsable du retour d'un nouveau exemple de votre classe. En revanche, _INIT_ ne retourne rien; c'est seulement responsable d'initialiser le par exemple après sa création.

En général, vous ne devriez pas avoir besoin de remplace _NEW_ à moins que vous ne soyez sous-classer un type immuable comme str, int, unicode ou tuple.

De: http://mail.python.org/pipermail/tutor/2008-avril/061426.html

Vous devriez considérer que ce que vous essayez de faire est généralement fait avec une Factory et c'est la meilleure façon de le faire. Utiliser _NEW_ n’est pas une solution propre, par conséquent envisagez l’utilisation d’une usine. Ici vous avez un bon exemple d’usine .

505
mpeterson

__new__ est une méthode de classe statique, tandis que __init__ est une méthode d'instance. __new__ doit d'abord créer l'instance pour que __init__ puisse l'initialiser. Notez que __init__ prend self en tant que paramètre. Tant que vous n'avez pas créé d'instance, il n'y a pas self.

Si j'ai bien compris, vous essayez d'implémenter singleton pattern en Python. Il y a plusieurs façons de le faire.

En outre, à partir de Python 2.6, vous pouvez utiliser la classe décorateurs

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

@singleton
class MyClass:
  ...
155
vartec

Dans les langages les plus connus OO, une expression telle que SomeClass(arg1, arg2) allouera une nouvelle instance, initialisera les attributs de l'instance, puis la renverra.

Dans les langages les plus connus OO, 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 du constructeur) pour définir les conditions initiales souhaitées. En Python, cela correspond à la méthode '__init__ de la classe'.

Le __new__ de Python n'est ni plus ni moins qu'une personnalisation similaire par classe de la partie "allouer une nouvelle instance". Cela vous permet bien entendu de faire des choses inhabituelles, telles que renvoyer une instance existante plutôt que d'en allouer une nouvelle. Donc, en Python, nous ne devrions pas vraiment considérer cette partie comme impliquant nécessairement une allocation; tout ce dont nous avons besoin est que __new__ crée une instance appropriée quelque part.

Mais cela ne représente toujours que la moitié du travail, et le système Python ne sait pas que parfois vous voulez exécuter l'autre moitié du travail (__init__) par la suite et que parfois vous ne le faites pas. t. Si vous voulez ce comportement, vous devez le dire explicitement.

Souvent, vous pouvez refactoriser pour que vous ayez seulement besoin de __new__, ou alors vous n'avez pas besoin de __new__, ou pour que __init__ se comporte différemment sur un objet déjà initialisé. Mais si vous le souhaitez vraiment, Python vous permet réellement de redéfinir "le travail", de sorte que 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 méthode __call__.

Une métaclasse n'est que la classe d'une classe. Et une méthode __call__ de la classe contrôle ce qui se passe lorsque vous appelez des instances de la classe. Donc, une méthode de métaclasse '__call__ contrôle ce qui se passe 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 . C'est le niveau auquel vous pouvez implémenter le plus élégamment un processus de création d'instance totalement non standard tel que le modèle singleton. En fait, avec moins de 10 lignes de code, vous pouvez implémenter une métaclasse Singleton qui ne vous oblige même pas à fusionner avec __new__. , et peut transformer n’importe quelle classe normalement normale en 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 garanti pour cette situation!

136
Ben

Pour citer la documentation :

Les implémentations typiques créent une nouvelle instance de la classe en appelant La méthode __new __ () de la superclasse utilisant "super (currentclass, cls) .__ new __ (cls [ ...])" avec les arguments appropriés, puis modifier l'instance nouvellement créée selon les besoins avant de la renvoyer.

...

Si __new __ () ne retourne pas une instance de cls, alors le nouveau La méthode __init __ () de l'instance ne sera pas appelée.

__new __ () est principalement destiné à autoriser les sous-classes de immuable types (tels que int, str ou Tuple) pour personnaliser la création d'instance.

20
tgray

Je me rends compte que cette question est assez ancienne mais j’avais un problème similaire…. Voici 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

10
Tobias

Lorsque __new__ renvoie l'instance de la même classe, __init__ est ensuite exécuté sur l'objet renvoyé. C'est à dire. vous ne pouvez PAS utiliser __new__ pour empêcher l'exécution de __init__. Même si vous retournez un objet créé précédemment à partir de __new__, il sera initialisé deux fois (triple, etc.) par __init__ encore et encore.

Voici l'approche générique du motif Singleton qui étend la réponse de vartec ci-dessus et le 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

La pleine histoire est ici .

Une autre approche, qui implique en fait __new__, consiste à 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"

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

8
Zaar Hai

Je pense que la réponse simple à cette question est que, si __new__ renvoie une valeur du même type que la classe, la fonction __init__ est exécutée, sinon elle ne le sera pas. Dans ce cas, votre code retourneA._dict('key')qui est la même classe quecls, donc __init__ sera exécuté.

8
PMN
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 propriété M._dict devient automatiquement accessible à partir de A sous la forme A._dict; veillez donc à ne pas l'écraser accidentellement.

5
Antony Hatchkins

__new__ devrait renvoyer une nouvelle instance vide d'une classe. __init__ est ensuite appelé pour initialiser cette instance. Vous n'appelez pas __init__ dans le "NOUVEAU" cas de __ne__, alors l'appel est fait pour vous. Le code qui appelle __new__ ne vérifie pas si __init__ a été appelé dans une instance particulière ou non, et ne le devrait pas non plus, car vous faites quelque chose de très inhabituel.

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 en tant que première chose dans __init__ et n'allez pas plus loin s'il l'a été.

4
Andrew Wilkinson

Une mise à jour de la réponse @AntonyHatchkins, vous souhaiterez probablement un dictionnaire d'instances distinct pour chaque classe du métatype, ce qui signifie que vous devriez avoir une méthode __init__ dans la métaclasse pour initialiser votre objet de classe avec ce dictionnaire au lieu de le rendre global pour 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()

Je suis allé de l'avant et j'ai mis à jour le code original avec une méthode __init__ et modifié la syntaxe en notation Python 3 (appel no-arg à super et métaclasse dans les arguments de la classe au lieu d'un attribut).

Quoi qu'il en soit, le point important ici est que votre initialiseur de classe (méthode __call__) n'exécutera ni __new__ ni __init__ si la clé est trouvée. Cela est beaucoup plus propre que d'utiliser __new__, ce qui vous oblige à marquer l'objet si vous souhaitez ignorer l'étape __init__ par défaut.

4
Mad Physicist

__init__ doit être considéré comme un constructeur simple dans les langues OO traditionnelles. Par exemple, si vous connaissez Java ou C++, le constructeur reçoit implicitement un pointeur sur sa propre instance. Dans le cas de Java, il s'agit de la variable this. Si l’on inspectait le code octet généré pour Java, on remarquerait deux appels. Le premier appel concerne une méthode "nouvelle", puis le suivant, la méthode init (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 est simplement une autre méthode de cette instance.

Désormais, dans le cas de Python, __new__ est une fonction ajoutée accessible à l'utilisateur. Java ne fournit 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 les types immuables en particulier dans le cas de Python. 

3
Guru Devanla

En référence à ce document

Lorsque vous sous-classez des types intégrés immuables tels que des nombres et des chaînes, et occasionnellement dans d'autres situations, la méthode statique new vient à portée de main. new est la première étape de la construction d'instance, appelée avant init

La méthode new est appelée avec la classe sous la forme premier argument; sa responsabilité est de renvoyer une nouvelle instance de cela classe. 

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

Il y a 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 (même si dans certains cas, vous pouvez vous échapper en appelant new d'une classe de base).

En ce qui concerne ce que vous souhaitez réaliser, vous trouverez également dans la même documentation des informations sur le modèle 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 à partir 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:
...
3
octoback

Creuser un peu plus profondément dans cela!

Le type d'une classe générique dans CPython est type et sa classe de base est Object (sauf si vous définissez explicitement une autre classe de base telle qu'une métaclasse). La séquence des appels de bas niveau peut être trouvée ici . La première méthode appelée est type_call qui appelle ensuite tp_new puis tp_init

La partie intéressante ici est que tp_new appellera la nouvelle méthode Object (classe de base) object_new qui effectue un tp_alloc (PyType_GenericAlloc) qui alloue la mémoire à l'objet :)

À ce stade, l'objet est créé en mémoire, puis la méthode __init__ est appelée. Si __init__ n'est pas implémenté dans votre classe, le object_init est appelé et il ne fait rien :)

Ensuite, type_call renvoie simplement l'objet qui se lie à votre variable.

3
xpapazaf

Le __init__ est appelé après le __new__. Ainsi, 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__, une personne ignorant ce fait peut commencer par adapter le __init__ et transférer l'appel à la sous-classe __init__. Cette convention d’appel __init__ après __new__ permet d’obtenir les résultats escomptés.

Le __init__ doit toujours autoriser tous les paramètres dont la superclasse __new__ est nécessaire, mais à défaut, cela entraînera généralement une erreur d’exécution évidente. Et le __new__ devrait probablement autoriser explicitement *args et '** kw', afin de préciser que l'extension est OK.

C'est généralement une mauvaise forme d'avoir à la fois __new__ et __init__ dans la même classe au même niveau d'héritage, à cause du comportement décrit par l'affiche originale.

1
Jay Obermark

Cependant, je suis un peu confus quant à la raison pour laquelle init est toujours appelé après new .

Je pense que l'analogie C++ serait utile ici: (A) new alloue simplement de la mémoire pour 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 . (B) init initialise les variables internes de l’objet à des valeurs spécifiques défaut).

1
HAltos

Cependant, je suis un peu confus quant à la raison pour laquelle __init__ est toujours appelé après __new__.

Pas vraiment une raison autre que le fait que ça se passe comme ça. __new__ n'a pas la responsabilité d'initialiser la classe, mais une autre méthode (__call__, éventuellement - je ne sais pas avec certitude).

Je ne m'y attendais pas. Quelqu'un peut-il me dire pourquoi cela se produit et comment je vais implémenter cette fonctionnalité autrement? (mis à part la mise en œuvre dans le __new__ qui semble assez hacky).

Vous pourriez faire en sorte que __init__ ne fasse rien s'il a déjà été initialisé ou écrire une nouvelle métaclasse avec un nouveau __call__ n'appelant que __init__ sur de nouvelles instances et renvoyant simplement __new__(...).

0
Devin Jeanpierre

La raison simple est que new 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 doit être appelé avant init.

0
ZQ Hu