2013-03-06 21 views
56

Per esempio io ho una classe di base come segue:Come posso creare dinamicamente classi derivate da una classe base

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

da questa classe mi derivano diverse altre classi, per esempio

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

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

C'è un modo divinatorio bello creare le classi dinamicamente da una chiamata di funzione che mette la nuova classe nella mia attuale portata, come:

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

Come ci saranno commenti e domande Perché Ho bisogno di questo: le classi derivate hanno tutte la stessa struttura interna con la differenza, che il costruttore prende un numero di argomenti precedentemente non definiti. Ad esempio, MyClass prende le parole chiave a mentre il costruttore della classe TestClass accetta b e c.

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

ma non devono mai utilizzare il tipo della classe come argomento di input come

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

ho avuto questo lavoro, ma avrebbe preferito il contrario, vale a dire oggetti di classe creati dinamicamente.

+0

Giusto per sicurezza, vuoi cambiare il tipo di istanza in base agli argomenti forniti? Come se io dessi un 'a' sarà sempre' MyClass' e 'TestClass' non prenderà mai un' a'? Perché non dichiarare solo tutti e 3 gli argomenti in 'BaseClass .__ init __()' ma li imposti tutti come predefiniti come 'Nessuno '? 'def __init __ (self, a = None, b = None, C = None)'? – acattle

+0

Non posso dichiarare nulla nella classe base, dato che non conosco tutti gli argomenti che potrei usare. Potrei avere 30 diverse clase con 5 argomenti diversi, quindi dichiarare 150 argomenti nel costrutto non è una soluzione. – Alex

risposta

90

Questo bit di codice consente di creare nuove classi con dinamiche nomi e nomi di parametro. La verifica dei parametri in __init__ solo non consente parametri ignoti, se avete bisogno di altre verifiche, come tipo, o che sono obbligatorie, basta aggiungere la logica lì:

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 

E questo funziona in questo modo, per esempio:

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

vedo che stai chiedendo per inserire i nomi dinamici nel campo di applicazione di denominazione - ora, che non è considerato un buon p ractice in Python - è possibile avere sia nomi delle variabili, noti al momento di codifica, o dati - ei nomi imparato in runtime sono più "dati" che "variabili" -

Quindi, si può solo aggiungere le classi per un dizionario e usarli da lì:

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

e se il vostro disegno ha bisogno assolutamente i nomi a venire portata, basta fare lo stesso, ma utilizzare il dizionario restituito dalla globals() chiamata invece di un dizionario di arbitrario:

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

(Sarebbe infatti possibile che la funzione di classe class inserisse dinamicamente il nome nello scope globale del chiamante, ma questa è una pratica ancora peggiore e non è compatibile con le implementazioni Python.Il modo per farlo sarebbe ottenere il frame di esecuzione del chiamante, tramite sys._getframe(1) e impostare il nome della classe nel dizionario globale del frame nell'attributo f_globals).

aggiornamento, tl; dr: Questa risposta era diventata popolare, ancora molto specifica per il corpo di domanda. La risposta generale su come "dinamicamente creare classi derivate da una classe base" in Python è una semplice chiamata al type passando il nuovo nome di classe, una tupla con il baseclass (i) e il corpo __dict__ per la nuova classe -come questo:

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

aggiornamento
Chi avesse bisogno di questo dovrebbe anche controllare il progetto dill - che afferma di essere in grado di fare la serializzazione e classi di deserializzare proprio come salamoia fa a oggetti ordinari, e aveva vissuto ad esso in alcuni dei miei test.

+1

Se ricordo male, 'BaseClass .__ init __()' sarebbe meglio del più generale 'super (auto .__ classe __) .__ init __()', che suona più bene quando le nuove classi sono sottoclasse. (Riferimento: http://rhettinger.wordpress.com/2011/05/26/super-considered-super/) – EOL

+0

@EOL: sarebbe per le classi dichiarate staticamente - ma poiché non si ha il nome effettivo della classe in hardcode come primo parametro di Super, ciò richiederebbe molto ballare in giro. Prova a sostituirlo con 'super' sopra e crea una sottoclasse di una classe creata dinamicamente per capirlo; E, d'altra parte, in questo caso puoi avere la classe di base come un oggetto generale da cui chiamare "__init__". – jsbueno

+0

Ora ho avuto un po 'di tempo per esaminare la soluzione suggerita, ma non è proprio quello che voglio. In primo luogo, sembra che '__init__' di' BaseClass' sia chiamato con un argomento, ma in effetti 'BaseClass .__ init__' prende sempre un elenco arbitrario di argomenti di parole chiave. In secondo luogo, la soluzione sopra imposta tutti i nomi dei parametri consentiti come attributi, che non è quello che voglio. QUALSIASI argomento DEVE andare a 'BaseClass', ma quale so quando creo la classe derivata. Probabilmente aggiornerò la domanda o chiederò una più precisa per renderla più chiara. – Alex

60

type() è la funzione che crea classi (in particolare sotto-classi):

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

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 
+0

Nel tentativo di comprendere questo esempio usando Python 2.7, ho ottenuto un 'TypeError' che diceva' __init __() richiede esattamente 2 argomenti (1 dato) '. Ho trovato che aggiungere qualcosa (qualcosa?) Per riempire il vuoto sarebbe sufficiente. Ad esempio, 'obj = SubClass ('foo')' funziona senza errori. – DaveL17

+0

Questo è normale, poiché 'Sottoclassella 'è una sottoclasse di' BaseClass' nella domanda e 'BaseClass' accetta un parametro (' classtype', che è '' foo'' nell'esempio). – EOL

Problemi correlati