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?
Réponses:
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 .la source
__new__
une classe Factory, qui est devenue assez propre, alors merci pour votre contribution.__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.__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__
prendself
comme paramètre. Tant que vous ne créez pas d'instance, il n'y en a pasself
.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 .
la source
MyClass
est 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 interromptisinstance
/issubclass
vérifie, l'accès aux attributs / méthodes de classe directementMyClass.something
, nommer la classe dans lessuper
appels, etc., etc. Que ce soit un problème ou non dépend de la classe à laquelle vous l'appliquez (et le reste du programme).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 celaSomeClass(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 uneSingleton
mé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
!Cependant, c'est probablement une magie plus profonde que ce qui est vraiment justifié dans cette situation!
la source
Pour citer la documentation :
la source
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é.__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 .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:
J'ai utilisé cette page comme ressource http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html
la source
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 retourneA._dict('key')
qui est de la même classe quecls
, donc__init__
sera exécuté.la source
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:
L'histoire complète est ici .
Une autre approche, qui consiste en
__new__
fait à utiliser des méthodes de classe:Veuillez faire attention, avec cette approche, vous devez décorer TOUTES vos méthodes
@classmethod
, car vous n'utiliserez jamais d'instance réelle deMyClass
.la source
Se référant à ce document :
Concernant ce que vous souhaitez réaliser, il y a aussi dans le même doc des informations sur le motif Singleton
vous pouvez également utiliser cette implémentation de PEP 318, en utilisant un décorateur
la source
init
de__new__
sons vraiment hacky. C'est à cela que servent les métaclasses.les sorties:
NB En tant qu'effet secondaire, la
M._dict
propriété devient automatiquement accessible à partirA
deA._dict
ce moment- là, veillez donc à ne pas l'écraser accidentellement.la source
__init__
méthode qui définitcls._dict = {}
. Vous ne voulez probablement pas un dictionnaire partagé par toutes les classes de ce métatype (mais +1 pour l'idée).__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é.
la source
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.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 .la source
Creuser un peu plus en profondeur!
Le type d'une classe générique dans CPython est
type
et sa classe de base estObject
(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 celletype_call
qui appelletp_new
ensuite, puistp_init
.La partie intéressante ici est que
tp_new
va appeler laObject
nouvelle méthode de (classe de base)object_new
qui fait untp_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_call
Retourne ensuite simplement l'objet qui se lie à votre variable.la source
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 lathis
variable. 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.la source
Je pense que l'analogie C ++ serait utile ici:
__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.__init__
initialiser les variables internes de l'objet à des valeurs spécifiques (peut être par défaut).la source
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*args
et '** 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.la source
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).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__(...)
.la source
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 .
la source
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
Exemples de classes
Utilisé
Essayez-le en ligne!
la source