Existe-t-il un moyen de contourner le constructeur __init__
d'une classe en python?
Exemple:
class A(object):
def __init__(self):
print "FAILURE"
def Print(self):
print "YEHAA"
Maintenant, je voudrais créer une instance de A
. Cela pourrait ressembler à ceci, mais cette syntaxe n'est pas correcte.
a = A
a.Print()
ÉDITER:
Un exemple encore plus complexe:
Supposons que j'ai un objet C
, dans le but de stocker un seul paramètre et de faire quelques calculs avec lui. Cependant, le paramètre n'est pas transmis en tant que tel, mais il est intégré dans un énorme fichier de paramètres. Cela pourrait ressembler à ceci:
class C(object):
def __init__(self, ParameterFile):
self._Parameter = self._ExtractParamterFile(ParameterFile)
def _ExtractParamterFile(self, ParameterFile):
#does some complex magic to extract the right parameter
return the_extracted_parameter
Maintenant, je voudrais vider et charger une instance de cet objet C
. Cependant, lorsque je charge cet objet, je n'ai qu'une seule variable self._Parameter
et je ne peux pas appeler le constructeur, car il attend le fichier de paramètres.
@staticmethod
def Load(file):
f = open(file, "rb")
oldObject = pickle.load(f)
f.close()
#somehow create newObject without calling __init__
newObject._Parameter = oldObject._Parameter
return newObject
En d'autres termes, il n'est pas possible de créer une instance sans passer le fichier de paramètres. Dans mon "vrai" cas, cependant, ce n'est pas un fichier de paramètres mais une énorme quantité de données que je ne veux certainement pas transporter en mémoire ni même stocker sur disque.
Et puisque je veux retourner une instance de C
à partir de la méthode Load
je dois en quelque sorte appeler le constructeur.
VIEILLE ÉDITION:
Un exemple plus complexe, qui explique pourquoi Je pose la question:
class B(object):
def __init__(self, name, data):
self._Name = name
#do something with data, but do NOT save data in a variable
@staticmethod
def Load(self, file, newName):
f = open(file, "rb")
s = pickle.load(f)
f.close()
newS = B(???)
newS._Name = newName
return newS
Comme vous pouvez le voir, puisque data
n'est pas stocké dans une variable de classe, je ne peux pas le passer à __init__
. Bien sûr, je pourrais simplement le stocker, mais que se passe-t-il si les données sont un objet énorme, que je ne veux pas transporter tout le temps en mémoire ou même les enregistrer sur disque?
Vous pouvez contourner __init__
en appelant __new__
directement. Ensuite, vous pouvez créer un objet du type donné et appeler une méthode alternative pour __init__
. C'est quelque chose que pickle
ferait.
Cependant, je tiens tout d'abord à souligner que c'est quelque chose que vous ne devriez pas faites et ce que vous voulez vous essayez d'atteindre, il existe de meilleures façons de le faire, dont certaines ont été mentionnées dans les autres réponses. En particulier, c'est une mauvaise idée de ne pas appeler __init__
.
Lorsque des objets sont créés, cela se produit plus ou moins:
a = A.__new__(A, *args, **kwargs)
a.__init__(*args, **kwargs)
Vous pouvez sauter la deuxième étape.
Voici pourquoi vous ne devriez pas faites ceci: Le but de __init__
est d'initialiser l'objet, de remplir tous les champs et de s'assurer que __init__
les méthodes des classes parentes sont également appelées. Avec pickle
c'est une exception car il essaie de stocker toutes les données associées à l'objet (y compris any champs/variables d'instance qui sont définies pour l'objet), et ainsi de suite qui a été défini par __init__
l'heure précédente serait restaurée au cornichon, il n'est pas nécessaire de la rappeler.
Si vous sautez __init__
et utiliser un autre initialiseur, vous auriez une sorte de duplication de code - il y aurait deux endroits où les variables d'instance sont remplies, et il est facile de manquer l'un d'eux dans l'un des initialiseurs ou de faire accidentellement les deux remplir les champs agissent différemment. Cela donne la possibilité de bogues subtils qui ne sont pas si triviaux à tracer (vous devez savoir quel initialiseur a été appelé), et le code sera plus difficile à maintenir. Sans oublier que vous seriez dans un pétrin encore plus grand si vous utilisez l'héritage - les problèmes remonteront la chaîne d'héritage, car vous devrez utiliser cet initialiseur alternatif partout dans la chaîne.
En faisant cela, vous seriez plus ou moins en train de remplacer la création d'instance de Python et de créer la vôtre. Python le fait déjà assez bien pour vous, pas besoin d'aller le réinventer et cela confondra les gens qui utilisent votre code.
Voici ce qu'il faut faire à la place: Utilisez un seul __init__
méthode qui doit être appelée pour toutes les instanciations possibles de la classe qui initialise correctement toutes les variables d'instance. Pour différents modes d'initialisation, utilisez l'une des deux approches:
__init__
qui gère vos cas en utilisant des arguments facultatifs.__init__
), comme l'a montré Roman Bodnarchuk , tout en effectuant un travail supplémentaire ou autre. Il est préférable de transmettre toutes les données à la classe (et __init__
le gère), mais si cela est impossible ou gênant, vous pouvez définir certaines variables d'instance après la création de l'instance et __init__
a terminé l'initialisation.Si __init__
a une étape facultative (par exemple, comme le traitement de cet argument data
, bien que vous deviez être plus spécifique), vous pouvez soit en faire un argument facultatif, soit créer une méthode normale qui effectue le traitement ... ou les deux.
Utilisez classmethod
décorateur pour votre méthode Load
:
class B(object):
def __init__(self, name, data):
self._Name = name
#store data
@classmethod
def Load(cls, file, newName):
f = open(file, "rb")
s = pickle.load(f)
f.close()
return cls(newName, s)
Vous pouvez donc faire:
loaded_obj = B.Load('filename.txt', 'foo')
Éditer:
Quoi qu'il en soit, si vous souhaitez toujours omettre __init__
méthode, essayez __new__
:
>>> class A(object):
... def __init__(self):
... print '__init__'
...
>>> A()
__init__
<__main__.A object at 0x800f1f710>
>>> a = A.__new__(A)
>>> a
<__main__.A object at 0x800f1fd50>
En prenant votre question à la lettre, j'utiliserais des méta-classes:
class MetaSkipInit(type):
def __call__(cls):
return cls.__new__(cls)
class B(object):
__metaclass__ = MetaSkipInit
def __init__(self):
print "FAILURE"
def Print(self):
print "YEHAA"
b = B()
b.Print()
Cela peut être utile, par exemple pour copier des constructeurs sans polluer la liste des paramètres. Mais le faire correctement serait plus de travail et de soins que mon hack proposé.
Pas vraiment. Le but de __init__
est pour instancier un objet, et par défaut il ne fait vraiment rien. Si la __init__
la méthode ne fait pas ce que vous voulez, et ce n'est pas votre propre code à changer, vous pouvez cependant choisir de le désactiver. Par exemple, en prenant votre classe A, nous pourrions faire ce qui suit pour éviter d'appeler cela __init__
méthode:
def emptyinit(self):
pass
A.__init__ = emptyinit
a = A()
a.Print()
Cela changera dynamiquement quels __init__
méthode de la classe, en la remplaçant par un appel vide. Notez que ce n'est probablement PAS une bonne chose à faire, car cela n'appelle pas le __init__
méthode.
Vous pouvez également le sous-classer pour créer votre propre classe qui fait tout de même, sauf remplacer le __init__
méthode pour faire ce que vous voulez (peut-être rien).
Cependant, vous souhaitez peut-être simplement appeler la méthode à partir de la classe sans instancier un objet. Si tel est le cas, vous devriez regarder les décorateurs @ classmethod et @ staticmethod . Ils permettent juste ce type de comportement.
Dans votre code, vous avez placé le décorateur @staticmethod, qui ne prend pas d'argument self
. Peut-être ce qui pourrait être mieux à cet effet serait une @classmethod, qui pourrait ressembler davantage à ceci:
@classmethod
def Load(cls, file, newName):
# Get the data
data = getdata()
# Create an instance of B with the data
return cls.B(newName, data)
MISE À JOUR: l'excellente réponse de Rosh a souligné que vous POUVEZ éviter d'appeler __init__
en implémentant __new__
, que je n'étais pas au courant (bien que cela soit parfaitement logique). Merci Rosh!
Je lisais le livre de cuisine Python et il y a une section à ce sujet: l'exemple est donné en utilisant __new__
Pour contourner __init__()
>>> classe A: def __init __ (self, a): self.a = a > >> test = A ('a') >>> test.a 'a' >>> test_noinit = A .__ nouveau __ (A) >>> test_noinit.a Traceback (dernier appel le plus récent): Fichier "", ligne 1, dans test_noinit.a AttributeError: 'A' l'objet n'a pas d'attribut 'a' >>>
Cependant, je pense que cela ne fonctionne qu'en Python3. Ci-dessous fonctionne sous 2,7
>>> classe A: def __init __ (self, a): self.a = a > >> test = A .__ nouveau __ (A) Traceback (dernier appel en date): Fichier "", ligne 1, dans test = A. __new __ (A) AttributeError: la classe A n'a pas d'attribut '__new __' >>>
Comme je l'ai dit dans mon commentaire, vous pouvez changer votre __init__
méthode pour permettre la création sans donner de valeur à ses paramètres:
def __init__(self, p0, p1, p2):
# some logic
deviendrait:
def __init__(self, p0=None, p1=None, p2=None):
if p0 and p1 and p2:
# some logic
ou:
def __init__(self, p0=None, p1=None, p2=None, init=True):
if init:
# some logic