web-dev-qa-db-fra.com

Existe-t-il un moyen d'instancier une classe sans appeler __init__?

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?

56
Woltan

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:

  1. Prend en charge différentes signatures pour __init__ qui gère vos cas en utilisant des arguments facultatifs.
  2. Créez plusieurs méthodes de classe qui servent de constructeurs alternatifs. Assurez-vous qu'ils créent tous des instances de la classe de manière normale (c'est-à-dire en appelant __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.

54
Rosh Oxymoron

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>
20
Roman Bodnarchuk

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é.

9
user8335

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!

8
Ryan Hiebert

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 __' 
 >>> 
6
xbb

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
2
pajton