2013-03-06 16 views
56

Na przykład mam klasy bazowej, co następuje:Jak można dynamicznie utworzyć klas pochodnych z klasy bazowej

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

Od tej klasie I czerpią kilka innych klas, na przykład

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

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

Czy jest ładny, pythonic sposobem utworzenia tych klas dynamicznie przez wywołanie funkcji, która stawia nową klasę w moim dotychczasowym zakresie, jak:

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

Jak będą komentarze i pytania dlaczego Potrzebuję tego: Wszystkie wyprowadzone klasy mają dokładnie taką samą wewnętrzną strukturę, z tą różnicą, że konstruktor przyjmuje szereg wcześniej niezdefiniowanych argumentów. Na przykład MyClass przyjmuje słowa kluczowe: a, podczas gdy konstruktor klasy TestClass pobiera b i c.

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

Ale oni nigdy nie powinni używać typ klasy jako argumentu wejściowego jak

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

mam to do pracy, ale woleliby w drugą stronę, to znaczy dynamicznie tworzonych obiektów klasy.

+0

Czy chcesz mieć pewność, że typ instancji zmieni się w zależności od dostarczonych argumentów? Jakbym dał "a", zawsze będzie to "MyClass", a "TestClass" nigdy nie przyjmie "a"? Dlaczego po prostu nie zadeklarować wszystkich 3 argumentów w 'BaseClass .__ init __()', ale domyślnie wszystkie do 'None'? 'def __init __ (self, a = None, b = None, C = None)'? – acattle

+0

Nie mogę zadeklarować niczego w klasie bazowej, ponieważ nie znam wszystkich argumentów, których mógłbym użyć. Mogę mieć 30 różnych zdań z 5 różnymi argumentami, więc deklarowanie 150 argumentów w konstruktorze nie jest rozwiązaniem. – Alex

Odpowiedz

90

Ten fragment kodu pozwala na tworzenie nowych klas z dynamicznymi nazwisk i nazw parametrów. Weryfikacja parametrów w __init__ prostu nie pozwala nieznanych parametrów, jeśli potrzebujesz inne weryfikacje, jak typu, lub że są one obowiązkowe, wystarczy dodać logikę tam:

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 

I to działa w ten sposób, na przykład:

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

widzę, że proszą o włożenie dynamicznych nazw w zakresie nazewnictwa - teraz że nie jest uważane za dobry p ractice w Pythonie - albo masz nazw zmiennych, znane w momencie pisania kodu lub danych - a nazwy nauczył się w czasie wykonywania więcej „dane” niż „zmiennych” -

Tak, można po prostu dodać swoje klasy do słownika i wykorzystywać je stamtąd:

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

a jeśli projekt absolutnie potrzebuje nazwy przyjść w zakresie, prostu zrobić to samo, ale korzystać ze słownika zwrócony przez globals() rozmowy zamiast dowolnego słownika:

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

(W rzeczywistości funkcja fabryczna klasy może wstawiać nazwę dynamicznie na globalnym zasięgu dzwoniącego - ale to jest jeszcze gorsza praktyka i nie jest kompatybilna z innymi implementacjami Pythona.Aby to zrobić, należy uzyskać ramkę wykonania dzwoniącego, poprzez sys._getframe(1) i ustawić nazwę klasy w globalnym słowniku ramki w jej atrybucie f_globals).

aktualizacja, tl; dr: Ta odpowiedź stała się popularna, wciąż bardzo specyficzna dla ciała pytania. Ogólna odpowiedź na jak „dynamicznego tworzenia klas pochodnych z klasy bazowej” w Pythonie jest proste wywołanie type przekazując nową nazwę klasy, krotką z klasy bazowej (y) i ciała __dict__ dla nowej klasy -Jak to:

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

aktualizacja
Każdy potrzebuje ten powinien również sprawdzić projekt dill - twierdzi, że jest w stanie marynowane i unpickle klasy tak jak robi zalewie zwykłych przedmiotów, i żył z nim w niektóre z moich testów.

+1

Jeśli dobrze pamiętam, 'BaseClass .__ init __()' byłoby lepsze od bardziej ogólnej 'super (self .__ class __) .__ init __()', która gra ładniej, gdy nowe klasy są podklasy. (Odniesienia: http://rhettinger.wordpress.com/2011/05/26/super-considered-super/) – EOL

+0

@EOL: Byłoby to dla statycznie zadeklarowanych klas - ale ponieważ nie masz rzeczywistej nazwy klasy do hardcode jako pierwszy parametr dla Super wymagałby dużo tańca. Spróbuj zastąpić go 'super' powyżej i utwórz podklasę dynamicznie tworzonej klasy, aby to zrozumieć; Z drugiej strony, w tym przypadku możesz mieć klasę podstawową jako ogólny obiekt, który wywołuje '__init__'. – jsbueno

+0

Teraz miałem trochę czasu, aby przyjrzeć się sugerowanemu rozwiązaniu, ale nie jest to dokładnie to, czego chcę. Po pierwsze, wygląda na to, że '__init__' z' BaseClass' jest wywoływane z jednym argumentem, ale w rzeczywistości 'BaseClass .__ init__' zawsze bierze dowolną listę argumentów słów kluczowych. Po drugie, powyższe rozwiązanie ustawia wszystkie dozwolone nazwy parametrów jako atrybuty, co nie jest tym, czego chcę. DOWOLNY argument HAS, aby przejść do 'BaseClass', ale który znam podczas tworzenia klasy pochodnej. Prawdopodobnie zaktualizuję pytanie lub poproszę o bardziej precyzyjne pytanie, aby było jaśniejsze. – Alex

60

type() jest funkcja, która tworzy klas (w szczególności podklasy):

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

Próbując zrozumieć ten przykład za pomocą Pythona 2.7, otrzymałem "TypeError", który powiedział, że '__init __() przyjmuje dokładnie 2 argumenty (1 dane)'. Zauważyłem, że wystarczyłoby coś (cokolwiek?), By wypełnić lukę. Na przykład 'obj = SubClass ('foo')' działa bez błędu. – DaveL17

+0

Jest to normalne, ponieważ 'SubClass' jest podklasą' BaseClass' w pytaniu, a 'BaseClass' przyjmuje parametr (' classtype', który jest '' foo'' w twoim przykładzie). – EOL

Powiązane problemy