116 votes

Comment créer dynamiquement des classes dérivées à partir d'une classe de base ?

Par exemple, j'ai une classe de base comme suit :

class BaseClass(object):
    def __init__(self, classtype):
        self._type = classtype

De cette classe, je dérive plusieurs autres classes, par ex.

class TestClass(BaseClass):
    def __init__(self):
        super(TestClass, self).__init__('Test')

class SpecialClass(BaseClass):
    def __init__(self):
        super(TestClass, self).__init__('Special')

Existe-t-il un moyen agréable et pythonique de créer ces classes dynamiquement par un appel de fonction qui place la nouvelle classe dans mon champ d'application actuel, par exemple :

foo(BaseClass, "My")
a = MyClass()
...

Comme il y aura des commentaires et des questions sur la raison pour laquelle j'ai besoin de cela : Les classes dérivées ont toutes exactement la même structure interne, à la différence que le constructeur prend un certain nombre d'arguments non définis auparavant. Donc, par exemple, MyClass prend les mots-clés a alors que le constructeur de la classe TestClass prend b y c .

inst1 = MyClass(a=4)
inst2 = MyClass(a=5)
inst3 = TestClass(b=False, c = "test")

Mais ils ne devraient JAMAIS utiliser le type de la classe comme argument d'entrée, comme par exemple

inst1 = BaseClass(classtype = "My", a=4)

J'ai réussi à faire fonctionner cette méthode, mais je préférerais l'autre, c'est-à-dire des objets de classe créés dynamiquement.

168voto

jsbueno Points 22212

Ce bout de code vous permet de créer de nouvelles classes avec des noms et des noms de paramètres dynamiques. et des noms de paramètres dynamiques. La vérification des paramètres dans __init__ ne permet pas paramètres inconnus, si vous avez besoin d'autres vérifications, telles que type, ou qu'ils sont obligatoires, il suffit d'ajouter la logique ici :

class BaseClass(object):
    def __init__(self, classtype):
        self._type = classtype

def ClassFactory(name, argnames, BaseClass=BaseClass):
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            # here, the argnames variable is the one passed to the
            # ClassFactory call
            if key not in argnames:
                raise TypeError("Argument %s not valid for %s" 
                    % (key, self.__class__.__name__))
            setattr(self, key, value)
        BaseClass.__init__(self, name[:-len("Class")])
    newclass = type(name, (BaseClass,),{"__init__": __init__})
    return newclass

Et ça marche comme ça, par exemple :

>>> SpecialClass = ClassFactory("SpecialClass", "a b c".split())
>>> s = SpecialClass(a=2)
>>> s.a
2
>>> s2 = SpecialClass(d=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in __init__
TypeError: Argument d not valid for SpecialClass

Je vois que vous demandez l'insertion des noms dynamiques dans la portée de nommage -- maintenant, que n'est pas considéré comme une bonne pratique en Python - vous avez soit soit des noms de variables, connus au moment du codage, soit des données - et les noms appris en cours d'exécution sont plus des "données" que des "variables" -

Ainsi, vous pourriez simplement ajouter vos classes à un dictionnaire et les utiliser à partir de là :

name = "SpecialClass"
classes = {}
classes[name] = ClassFactory(name, params)
instance = classes[name](...)

Et si votre conception a absolument besoin que les noms entrent dans la portée, faites simplement la même chose, mais utilisez le dictionnaire renvoyé par la fonction globals() au lieu d'un dictionnaire arbitraire :

name = "SpecialClass"
globals()[name] = ClassFactory(name, params)
instance = SpecialClass(...)

(Il serait en effet possible pour la fonction de fabrique de classe d'insérer le nom dynamiquement dans la portée globale de l'appelant - mais c'est une pratique encore pire, et ce n'est pas compatible avec les implémentations Python. La façon de le faire serait d'obtenir le cadre d'exécution de l'appelant, par l'intermédiaire de sys._getframe(1) et en définissant le nom de la classe dans le dictionnaire global du cadre dans sa section f_globals ).

mise à jour, tl;dr : Cette réponse est devenue populaire, mais elle est très spécifique au corps de la question. La réponse générale sur la façon de "créer dynamiquement des classes dérivées à partir d'une classe de base" en Python est un simple appel à type en transmettant le nom de la nouvelle classe, un tuple avec la ou les classes de base et le nom de la classe de base. __dict__ corps pour la nouvelle classe - comme ceci :

>>> new_class = type("NewClassName", (BaseClass,), {"new_method": lambda self: ...})

mise à jour
Les personnes qui en ont besoin doivent également vérifier le aneth Il prétend être capable d'extraire et d'enlever les classes comme le fait Pickle pour les objets ordinaires, et il l'a fait dans certains de mes tests.

108voto

EOL Points 24342

type() est la fonction qui crée les classes et en particulier les sous-classes, comme dans la question :

def set_x(self, value):
    self.x = value

# type() takes as argument the new class name, its base
# classes, and its attributes:
SubClass = type('SubClass', (BaseClass,), {'set_x': set_x})
# (More methods can be put in SubClass, including __init__().)

obj = SubClass()
obj.set_x(42)
print obj.x  # Prints 42
print isinstance(obj, BaseClass)  # True

-5voto

AdefemiGreat Points 11

Pour créer une classe avec une valeur d'attribut dynamique, consultez le code ci-dessous. NB. Ce sont des extraits de code en langage de programmation python.

def create_class(attribute_data, **more_data): # define a function with required attributes
    class ClassCreated(optional extensions): # define class with optional inheritance
          attribute1 = adattribute_data # set class attributes with function parameter
          attribute2 = more_data.get("attribute2")

    return ClassCreated # return the created class

# use class

myclass1 = create_class("hello") # *generates a class*

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X