Na przykład mam następującą klasę bazową:
class BaseClass(object):
def __init__(self, classtype):
self._type = classtype
Z tej klasy wyprowadzam kilka innych klas, np
class TestClass(BaseClass):
def __init__(self):
super(TestClass, self).__init__('Test')
class SpecialClass(BaseClass):
def __init__(self):
super(TestClass, self).__init__('Special')
Czy istnieje ładny, pythonowy sposób dynamicznego tworzenia tych klas przez wywołanie funkcji, które umieszcza nową klasę w moim obecnym zakresie, na przykład:
foo(BaseClass, "My")
a = MyClass()
...
Ponieważ pojawią się komentarze i pytania, dlaczego tego potrzebuję: wszystkie klasy pochodne mają dokładnie taką samą strukturę wewnętrzną z tą różnicą, że konstruktor pobiera wiele wcześniej niezdefiniowanych argumentów. Na przykład MyClass
przyjmuje słowa kluczowe, a
podczas gdy konstruktor klasy TestClass
przyjmuje b
i c
.
inst1 = MyClass(a=4)
inst2 = MyClass(a=5)
inst3 = TestClass(b=False, c = "test")
Ale NIGDY nie powinni używać typu klasy jako argumentu wejściowego, takiego jak
inst1 = BaseClass(classtype = "My", a=4)
Mam to do pracy, ale wolałbym inny sposób, tj. Dynamicznie tworzone obiekty klas.
python
class
inheritance
Alex
źródło
źródło
a
, zawsze będzieMyClass
iTestClass
nigdy nie weźmiea
? Dlaczego po prostu nie zadeklarować wszystkich 3 argumentów w programie,BaseClass.__init__()
ale wszystkie domyślnie są ustawione naNone
?def __init__(self, a=None, b=None, C=None)
?Odpowiedzi:
Ten fragment kodu umożliwia tworzenie nowych klas z dynamicznymi nazwami i nazwami parametrów. Weryfikacja parametrów po
__init__
prostu nie zezwala na nieznane parametry, jeśli potrzebujesz innych weryfikacji, takich jak typ, lub że są one obowiązkowe, po prostu dodaj tam logikę: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
A to działa tak, 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 prosi o wstawienie nazw dynamicznych w zakresie nazewnictwa - teraz, że nie jest uważane za dobre praktyki w Pythonie - albo masz nazwy zmiennych, znane w momencie pisania kodu lub danych - i nazwiska nabyte w czasie pracy są bardziej " dane "niż" zmienne "-
Możesz więc po prostu dodać swoje klasy do słownika i używać ich z tego miejsca:
name = "SpecialClass" classes = {} classes[name] = ClassFactory(name, params) instance = classes[name](...)
A jeśli twój projekt absolutnie potrzebuje nazw, które wchodzą w zakres, po prostu zrób to samo, ale użyj słownika zwróconego przez
globals()
wywołanie zamiast dowolnego słownika:name = "SpecialClass" globals()[name] = ClassFactory(name, params) instance = SpecialClass(...)
(Rzeczywiście byłoby możliwe, aby funkcja fabryki klasy wstawiała nazwę dynamicznie do globalnego zakresu wywołującego - ale jest to jeszcze gorsza praktyka i nie jest kompatybilna z implementacjami Pythona. Sposobem na to byłoby uzyskanie wywołującego wykonanie ramki, poprzez sys._getframe (1) i ustawienie nazwy klasy w globalnym słowniku ramki w jej
f_globals
atrybucie).update, tl; dr: Ta odpowiedź stała się popularna, ale nadal jest bardzo specyficzna dla treści pytania. Ogólna odpowiedź na temat „dynamicznego tworzenia klas pochodnych z klasy bazowej” w Pythonie jest prostym wywołaniem
type
przekazania nowej nazwy klasy, krotki z__dict__
klasami podstawowymi i treścią nowej klasy - na przykład:>>> new_class = type("NewClassName", (BaseClass,), {"new_method": lambda self: ...})
aktualizacja
Każdy, kto tego potrzebuje, powinien również sprawdzić projekt koperkowy - twierdzi, że jest w stanie marynować i unpickle klasy, tak jak w przypadku zwykłych przedmiotów, i przeżył to w niektórych moich testach.
źródło
BaseClass.__init__()
ile dobrze pamiętam, byłoby lepiej jako bardziej ogólnesuper(self.__class__).__init__()
, które gra ładniej, gdy nowe klasy są podklasy. (Odniesienie: rhettinger.wordpress.com/2011/05/26/super-considered-super )super
powyższym i utwórz podklasę dynamicznie utworzonej klasy, aby to zrozumieć; Z drugiej strony, w tym przypadku możesz mieć klasę bazową jako ogólny obiekt, z którego chcesz wywołać__init__
.__init__
ofBaseClass
jest wywoływany z jednym argumentem, ale w rzeczywistościBaseClass.__init__
zawsze pobiera 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ę. KAŻDY argument MUSI przejśćBaseClass
, ale który z nich znam podczas tworzenia klasy pochodnej. Prawdopodobnie zaktualizuję pytanie lub poproszę o bardziej precyzyjne, aby było jaśniejsze.super()
wspomnianego daje dajeTypeError: must be type, not SubSubClass
. Jeśli dobrze rozumiem, pochodzi to z pierwszego argumentuself
programu__init__()
, czyli miejsca, w którym oczekujeSubSubClass
siętype
obiektu: wydaje się, że ma to związek z faktem, żesuper(self.__class__)
jest to niezwiązany super obiekt. Jaka jest jego__init__()
metoda? Nie jestem pewien, która taka metoda może wymagać pierwszego argumentu typutype
. Czy możesz wytłumaczyć? (Na marginesie: mojesuper()
podejście rzeczywiście nie ma tu sensu, ponieważ__init__()
ma zmienną sygnaturę.)type()
jest funkcją, która tworzy klasy (aw 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
źródło
TypeError
odpowiedź__init__() takes exactly 2 arguments (1 given)
. Stwierdziłem, że dodanie czegoś (czegokolwiek?), Aby wypełnić lukę, wystarczy. Na przykładobj = SubClass('foo')
działa bez błędów.SubClass
jest to podklasaBaseClass
w pytaniu iBaseClass
przyjmuje parametr (classtype
który jest'foo'
w twoim przykładzie).Aby utworzyć klasę z dynamiczną wartością atrybutu, sprawdź poniższy kod. NB. To są fragmenty kodu w języku programowania 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*
źródło