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?
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 .
__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:
...
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!
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.
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
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
.
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é.
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.
__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é.
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.
__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.
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:
...
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.
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.
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).
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__(...)
.
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.