2010-03-17 16 views
11

Je souhaite ajouter dynamiquement des attributs de classe à une super-classe. De plus, je veux créer des classes qui héritent dynamiquement de cette superclasse, et le nom de ces sous-classes devrait dépendre de l'entrée de l'utilisateur.Python: fabrique de classes utilisant l'entrée utilisateur comme noms de classes

Il existe une super-classe "Unit", à laquelle je peux ajouter des attributs lors de l'exécution. Cela fonctionne déjà.

def add_attr (cls, name, value): 
    setattr(cls, name, value) 

class Unit(object): 
    pass 

class Archer(Unit): 
    pass 

myArcher = Archer() 
add_attr(Unit, 'strength', 5) 
print "Strenght ofmyarcher: " + str(myArcher.strength) 
Unit.strength = 2 
print "Strenght ofmyarcher: " + str(myArcher.strength) 

Cela conduit à la sortie désirée:
Strenght ofmyarcher: 5
Strenght ofmyarcher: 2

Mais maintenant, je ne veux pas prédéfinir la sous-classe Archer, mais je préfère laisser la l'utilisateur décide comment appeler cette sous-classe. J'ai essayé quelque chose comme ceci:

class Meta(type, subclassname): 
    def __new__(cls, subclassname, bases, dct): 
    return type.__new__(cls, subclassname, Unit, dct) 

factory = Meta()  
factory.__new__("Soldier") 

mais pas de chance. Je suppose que je n'ai pas vraiment compris ce que nouveau fait ici. Ce que je veux à la suite est ici

class Soldier(Unit): 
    pass 

créé par l'usine. Et si j'appelle l'usine avec l'argument "Chevalier", j'aimerais qu'une classe Chevalier, sous-classe d'Unité, soit créée.

Des idées? Merci d'avance!
Bye
-Sano

Répondre

14

Pour créer une classe à partir d'un nom, utilisez l'instruction class et affectez le nom. Observez:

def meta(name): 
    class cls(Unit): 
     pass 

    cls.__name__ = name 
    return cls 

Maintenant je suppose que je devrais m'expliquer, et ainsi de suite. Lorsque vous créez une classe à l'aide de l'instruction de classe, elle est effectuée de manière dynamique: elle équivaut à appeler type().

Par exemple, les deux extraits font la même chose suivantes:

class X(object): pass 
X = type("X", (object,), {}) 

Le nom d'un class-- le premier argument de bien-- est affecté à __name__, et qui est essentiellement la fin de cette (la seule fois que __name__ est lui-même utilisé est probablement dans l'implémentation __repr__() par défaut). Pour créer une classe avec un nom dynamique, vous pouvez en fait appeler le type comme ça, ou vous pouvez juste changer le nom de la classe par la suite. La syntaxe class existe pour une raison, cependant - c'est pratique, et il est facile d'ajouter et de modifier les choses plus tard.Si vous vouliez ajouter des méthodes, par exemple, il serait

class X(object): 
    def foo(self): print "foo" 

def foo(self): print "foo" 
X = type("X", (object,), {'foo':foo}) 

et ainsi de suite. Donc, je conseillerais d'utiliser la déclaration de classe - si vous aviez su que vous pouviez le faire depuis le début, vous l'auriez probablement fait. Traiter avec type et ainsi de suite est un gâchis.

(Vous ne devriez pas, par ailleurs, appeler type.__new__() à la main, ne type())

+0

Merci Devin! Votre solution et celle de Felix semblent avoir un effet secondaire que je n'ai pas pris en compte: La nouvelle classe est un objet et doit être accrochée à quelque chose - stockée dans une varibale ou ajoutée à une liste ou à quelque chose. Je ne peux pas simplement créer la classe Knight, puis appeler le constructeur avec myKnight = Knight(). Plutôt, je dois faire quelque chose comme ceci: unitlist.append (meta ("Knight")) myKnight = unitlist [0]() Y a-t-il un moyen de contourner cela? Pour le dire franchement: Enregistrer la classe Knight à la même "position générale" la classe Unit est enregistrée lorsque sa définition est atteinte dans le code? – Sano98

+0

@ Sano98: Je ne sais pas vraiment ce que tu veux dire. Vous pouvez faire 'Knight = meta (" Knight ")', puis faire 'myKnight = Knight()'. Que voulez-vous exactement? –

+0

Merci, c'est exactement ce que je veux faire! Il suffit de l'enregistrer sous Knight ... Et puis c'est dans l'espace de noms global et je peux utiliser la classe comme si elle était codée en dur. Super merci. Je ne pensais tout simplement pas pouvoir appeler Chevalier() en sauvant la classe en tant que Chevalier, mais bien sûr, pourquoi pas? – Sano98

7

Jetez un oeil à la type() fonction builtin.

knight_class = type('Knight', (Unit,), {}) 
  • Premier paramètre: Nom de la nouvelle classe
  • Deuxième paramètre: Tuple des classes parent
  • Troisième paramètre: dictionnaire des attributs de classe.

Mais dans votre cas, si les sous-classes ne mettent pas en œuvre un comportement différent, donnant peut-être la classe Unit un attribut name est suffisante.

+0

Merci, Felix, ce que vous suggérez fonctionne aussi bien! – Sano98

+0

J'ai trouvé votre réponse très utile. Merci de l'avoir ajouté malgré les autres déjà présents! – Profane