web-dev-qa-db-fra.com

Python (et Python API C): __new__ contre __init__

La question que je m'apprête à poser semble être un doublon de l'utilisation par Python de __new__ et __init __? , mais quoi qu'il en soit, je ne sais toujours pas exactement quelle est la différence pratique entre __new__ Et __init__ Est.

Avant de vous précipiter pour me dire que __new__ Est pour créer des objets et __init__ Est pour initialiser des objets, laissez-moi être clair: Je comprends. En fait, cette distinction m'est tout à fait naturelle, car j'ai une expérience en C++ où nous avons placement new , qui sépare également l'allocation d'objets de l'initialisation.

Le tutoriel sur l'API Python C l'explique comme ceci:

Le nouveau membre est responsable de la création (par opposition à l'initialisation) des objets du type. Il est exposé dans Python comme méthode __new__(). ... Une raison pour implémenter une nouvelle méthode est d'assurer les valeurs initiales des variables d'instance .

Donc, oui - je obtenez ce que fait __new__, Mais malgré cela, je encore ne comprends pas pourquoi c'est utile en Python. L'exemple donné dit que __new__ Pourrait être utile si vous voulez "assurer les valeurs initiales des variables d'instance". Eh bien, n'est-ce pas exactement ce que fera __init__?

Dans le didacticiel de l'API C, un exemple est montré où un nouveau type (appelé "Noddy") est créé et la fonction __new__ Du type est définie. Le type Noddy contient un membre de chaîne appelé first, et ce membre de chaîne est initialisé en une chaîne vide comme suit:

static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    .....

    self->first = PyString_FromString("");
    if (self->first == NULL)
    {
       Py_DECREF(self);
       return NULL;
    }

    .....
}

Notez que sans la méthode __new__ Définie ici, nous aurions dû utiliser PyType_GenericNew, Qui initialise simplement tous les membres de variable d'instance à NULL. Ainsi, le seul avantage de la méthode __new__ Est que la variable d'instance commencera comme une chaîne vide, par opposition à NULL. Mais pourquoi est-ce toujours utile, car si nous voulions nous assurer que nos variables d'instance sont initialisées à une valeur par défaut, nous aurions pu le faire dans la méthode __init__?

116
Channel72

La différence provient principalement des types mutables vs immuables.

__new__ Accepte un type comme premier argument, et (généralement) renvoie une nouvelle instance de ce type. Ainsi, il convient à une utilisation avec des types mutables et immuables.

__init__ Accepte un instance comme premier argument et modifie les attributs de cette instance. Ceci est inapproprié pour un type immuable, car cela permettrait de les modifier après la création en appelant obj.__init__(*args).

Comparez le comportement de Tuple et list:

>>> x = (1, 2)
>>> x
(1, 2)
>>> x.__init__([3, 4])
>>> x # Tuple.__init__ does nothing
(1, 2)
>>> y = [1, 2]
>>> y
[1, 2]
>>> y.__init__([3, 4])
>>> y # list.__init__ reinitialises the object
[3, 4]

Quant à savoir pourquoi ils sont séparés (à part de simples raisons historiques): les méthodes __new__ Nécessitent un tas de passe-partout pour bien fonctionner (la création initiale de l'objet, puis se rappeler de renvoyer l'objet à la fin). Les méthodes __init__, En revanche, sont extrêmement simples, car vous définissez simplement les attributs que vous devez définir.

Mis à part que les méthodes __init__ Sont plus faciles à écrire et la distinction mutable vs immuable notée ci-dessus, la séparation peut également être exploitée pour rendre l'appel de la classe parente __init__ Dans les sous-classes facultatif en définissant tout absolument requis invariants d'instance dans __new__. C'est généralement une pratique douteuse - il est généralement plus clair d'appeler simplement les méthodes de la classe parent __init__ Si nécessaire.

130
ncoghlan

Il existe probablement d'autres utilisations de __new__ mais il y en a un très évident: vous ne pouvez pas sous-classer un type immuable sans utiliser __new__. Par exemple, supposons que vous vouliez créer une sous-classe de Tuple qui ne peut contenir que des valeurs intégrales entre 0 et size.

class ModularTuple(Tuple):
    def __new__(cls, tup, size=100):
        tup = (int(x) % size for x in tup)
        return super(ModularTuple, cls).__new__(cls, tup)

Vous ne pouvez tout simplement pas faire cela avec __init__ - si vous avez essayé de modifier self dans __init__, l'interprète se plaindrait que vous essayez de modifier un objet immuable.

35
senderle

__new__() peut renvoyer des objets de types autres que la classe à laquelle il est lié. __init__() initialise uniquement une instance existante de la classe.

>>> class C(object):
...   def __new__(cls):
...     return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5
30

Pas une réponse complète mais peut-être quelque chose qui illustre la différence.

__new__ sera toujours appelé lorsqu'un objet doit être créé. Dans certaines situations, __init__ ne sera pas appelé. Par exemple, lorsque vous décompactez des objets d'un fichier de pickle, ils seront alloués (__new__) mais non initialisé (__init__).

11
Noufal Ibrahim

Je veux juste ajouter un mot sur l'intention (par opposition au comportement) de définir __new__ contre __init__.

Je suis tombé sur cette question (entre autres) lorsque j'essayais de comprendre la meilleure façon de définir une usine de classe. J'ai réalisé que l'une des façons dont __new__ est conceptuellement différent de __init__ est le fait que l'avantage de __new__ est exactement ce qui a été dit dans la question:

Ainsi, le seul avantage de la méthode __new__ est que la variable d'instance commencera comme une chaîne vide, par opposition à NULL. Mais pourquoi est-ce toujours utile, car si nous voulions nous assurer que nos variables d'instance sont initialisées à une valeur par défaut, nous aurions pu le faire dans la méthode __init__?

Compte tenu du scénario indiqué, nous nous soucions des valeurs initiales des variables d'instance lorsque l'instance est en réalité une classe elle-même. Donc, si nous créons dynamiquement un objet de classe au moment de l'exécution et que nous devons définir/contrôler quelque chose de spécial concernant les instances suivantes de cette classe en cours de création, nous définirions ces conditions/propriétés dans un __new__ méthode d'une métaclasse.

J'étais confus à ce sujet jusqu'à ce que je pense à l'application du concept plutôt qu'à sa signification. Voici un exemple qui, nous l'espérons, clarifierait la différence:

a = Shape(sides=3, base=2, height=12)
b = Shape(sides=4, length=2)
print(a.area())
print(b.area())

# I want `a` and `b` to be an instances of either of 'Square' or 'Triangle'
# depending on number of sides and also the `.area()` method to do the right
# thing. How do I do that without creating a Shape class with all the
# methods having a bunch of `if`s ? Here is one possibility

class Shape:
    def __new__(cls, sides, *args, **kwargs):
        if sides == 3:
            return Triangle(*args, **kwargs)
        else:
            return Square(*args, **kwargs)

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return (self.base * self.height) / 2

class Square:
    def __init__(self, length):
        self.length = length

    def area(self):
        return self.length*self.length

Notez que ce n'est qu'un exemple de démonstration. Il existe plusieurs façons d'obtenir une solution sans recourir à une approche d'usine de classe comme ci-dessus et même si nous choisissons d'implémenter la solution de cette manière, il y a quelques mises en garde par souci de concision (par exemple, en déclarant explicitement la métaclasse )

Si vous créez une classe régulière (a.k.a une non-métaclasse), alors __new__ n'a pas vraiment de sens à moins qu'il ne s'agisse d'un cas particulier comme le scénario mutable contre immuable dans réponse de ncoghlan réponse (qui est essentiellement un exemple plus spécifique du concept de définition des valeurs/propriétés initiales du classe/type en cours de création via __new__ à initialiser ensuite via __init__).

2
lonetwin