La question que je m'apprête à poser semble être une copie de l'utilisation par Python de __new__ et __init__? , mais peu importe, je ne sais toujours pas exactement quelle est la différence pratique entre __new__
et __init__
.
Avant de vous précipiter pour me dire que __new__
c'est pour créer des objets et __init__
c'est pour initialiser des objets, laissez-moi être clair: je comprends cela. En fait, cette distinction est tout à fait naturelle pour moi, car j'ai de l'expérience en C ++ où nous avons un placement nouveau , qui sépare de la même manière l'allocation d'objets de l'initialisation.
Le didacticiel de l'API Python C l' explique comme suit:
Le nouveau membre est responsable de la création (par opposition à l'initialisation) des objets du type. Il est exposé en Python comme
__new__()
méthode. ... Une des raisons d'implémenter une nouvelle méthode est de garantir les valeurs initiales des variables d'instance .
Donc, oui - je comprends ce qui __new__
fait, mais malgré cela, je ne comprends toujours pas pourquoi c'est utile en Python. L'exemple donné dit que cela __new__
peut être utile si vous voulez "garantir les valeurs initiales des variables d'instance". Eh bien, n'est-ce pas exactement ce qui __init__
va faire?
Dans le didacticiel de l'API C, un exemple est montré où un nouveau type (appelé "Noddy") est créé et la __new__
fonction du type est définie. Le type Noddy contient un membre de chaîne appelé first
, et ce membre de chaîne est initialisé à une chaîne vide comme ceci:
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 __new__
méthode définie ici, nous devrions utiliser PyType_GenericNew
, qui initialise simplement tous les membres de la variable d'instance à NULL. Ainsi, le seul avantage de la __new__
méthode est que la variable d'instance démarre comme une chaîne vide, par opposition à NULL. Mais pourquoi est-ce toujours utile, puisque 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 __init__
méthode?
la source
__new__
n'est pas passe-partout, car le passe-partout ne change jamais. Parfois, vous devez remplacer ce code particulier par quelque chose de différent.super
appel) et renvoyer l'instance sont des éléments nécessaires de toute__new__
implémentation, et le «passe-partout» auquel je fais référence. En revanche,pass
est une implémentation valide pour__init__
- il n'y a aucun comportement requis.Il existe probablement d'autres utilisations de,
__new__
mais il y en a une qui est vraiment évidente: 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 comprises entre 0 etsize
.Vous ne pouvez tout simplement pas faire cela avec
__init__
- si vous essayez de modifierself
dans__init__
, l'interpréteur se plaindra que vous essayez de modifier un objet immuable.la source
__new__
. Nous passonscls
explicitement à__new__
car, comme vous pouvez le lire ici, nécessite__new__
toujours un type comme premier argument. Il renvoie ensuite une instance de ce type. Nous ne renvoyons donc pas une instance de la superclasse - nous renvoyons une instance decls
. Dans ce cas, c'est exactement la même chose que si on l'avait dittuple.__new__(ModularTuple, tup)
.__new__()
peut renvoyer des objets de types autres que la classe à laquelle il est lié.__init__()
n'initialise qu'une instance existante de la classe.la source
__init__
méthodes qui contiennent du code qui ressemble àself.__class__ = type(...)
. Cela fait que l'objet est d'une classe différente de celle que vous pensiez créer. Je ne peux pas réellement le changer en unint
comme vous l'avez fait ... J'obtiens une erreur sur les types de tas ou quelque chose ... mais mon exemple de l'attribution à une classe créée dynamiquement fonctionne.__init__()
est appelé. Par exemple, dans la réponse de lonetwin , l'unTriangle.__init__()
ou l' autreSquare.__init__()
est appelé automatiquement en fonction du type de__new__()
retour. D'après ce que vous dites dans votre réponse (et j'ai lu cela ailleurs), il ne semble pas que l'un ou l'autre devrait l'être car ilShape.__new__()
ne renvoie pas une instance decls
(ni une sous-classe de celle-ci).__init__()
méthodes de la réponse de lonetwin sont appelées lorsque les objets individuels sont instanciés (c'est-à-dire lorsque leur__new__()
méthode retourne), et non lors duShape.__new__()
retour.Shape.__init__()
(s'il en avait un) ne serait pas appelé. Maintenant, tout est plus logique ...:¬)
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éé. Il y a des situations où__init__
ne sera pas appelé. Un exemple est que lorsque vous désélectionnez des objets d'un fichier pickle, ils seront alloués (__new__
) mais pas initialisés (__init__
).la source
__new__
méthode est de créer (cela implique une allocation de mémoire) une instance de la classe et de la renvoyer. L'initialisation est une étape distincte et c'est ce qui est généralement visible par l'utilisateur. Veuillez poser une question distincte si vous rencontrez un problème spécifique.Je veux juste ajouter un mot sur l' intention (par opposition au comportement) de définir
__new__
versus__init__
.Je suis tombé sur cette question (entre autres) lorsque j'essayais de comprendre la meilleure façon de définir une fabrique de classes. J'ai réalisé que l'une des façons dont
__new__
est conceptuellement différente de__init__
est le fait que l'avantage de__new__
est exactement ce qui a été énoncé dans la question: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 sur les instances suivantes de cette classe en cours de création, nous définirions ces conditions / propriétés dans une
__new__
méthode d'une métaclasse.J'étais confus à ce sujet jusqu'à ce que je pense réellement à l'application du concept plutôt qu'à la signification de celui-ci. Voici un exemple qui, espérons-le, rendrait la différence claire:
Notez que ce n'est qu'un exemple de démonstration. Il y a plusieurs façons d'obtenir une solution sans recourir à une approche de fabrique de classes 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 pour des raisons de brièveté (par exemple, déclarer la métaclasse explicitement )
Si vous créez une classe régulière (alias une non-métaclasse), cela
__new__
n'a pas vraiment de sens à moins que ce ne soit un cas spécial comme le scénario mutable versus immuable dans la réponse de ncoghlan (qui est essentiellement un exemple plus spécifique du concept de définition les valeurs / propriétés initiales de la classe / du type en cours de création via__new__
pour être ensuite initialisées via__init__
).la source