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