Jak mogę dynamicznie tworzyć klasy pochodne z klasy bazowej

Na przykład mam klasę bazową w następujący sposób:

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

Z tej klasy wywodzę 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, pythoniczny sposób tworzenia tych klas dynamicznie przez wywołanie funkcji, które umieszcza nową klasę w moim bieżącym zakresie, jak:

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

Ponieważ będą komentarze i pytania, po co mi to: wszystkie klasy pochodne mają dokładnie tę samą strukturę wewnętrzną z tą różnicą, że konstruktor pobiera liczbę wcześniej nieokreślone argumenty. 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 powinny używać typu klasy jako argumentu wejściowego, takiego jak

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

Udało mi się to zrobić, ale wolałabym inny sposób, tj. dynamicznie tworzone obiekty klasowe.

Author: Alex, 2013-03-06

2 answers

Ten bit kodu pozwala na tworzenie nowych klas z dynamicznym nazwy i nazwy parametrów. Weryfikacja parametru w __init__ po prostu nie pozwala nieznane parametry, jeśli potrzebujesz innych weryfikacji, takich jak wpisz, lub że są 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

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 prosisz o wstawienie nazw dynamicznych w zakresie nazewnictwa -- teraz, to nie jest uważane za dobrą praktykę w Pythonie - albo masz nazwy zmiennych, znane w czasie kodowania, lub Dane - i nazwy poznane w trybie runtime są bardziej "dane" niż "zmienne" -

Więc możesz po prostu dodać swoje klasy do słownika i używać ich stamtąd:

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

I jeśli twój projekt absolutnie potrzebuje nazw w zakresie, 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 to możliwe dla fabryki klas funkcja dynamicznego wstawiania nazwy w globalnym zasięgu wywołującego-ale to jeszcze gorsza praktyka i nie jest kompatybilna we wszystkich implementacjach Pythona. Aby to zrobić, należy uzyskać ramkę wykonawczą wywołującego, poprzez sys._getframe (1) i ustawienie nazwy klasy w globalnym słowniku ramki w atrybutie f_globals).

Update, TL; dr: ta odpowiedź stała się popularna, nadal jest bardzo specyficzna dla ciała pytającego. Ogólna odpowiedź na to, jak "dynamiczne tworzenie klas pochodnych z klasy bazowej" w Pythonie jest proste wywołanie type przekazujące nową nazwę klasy, krotkę z baseclass (es) i ciało __dict__ dla nowej klasy -jak to:

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

Update
Każdy, kto tego potrzebuje, powinien również sprawdzić projekt dill - twierdzi, że potrafi marynować i rozpakowywać klasy, tak jak pickle robi to ze zwykłymi obiektami, i przeżył to w niektórych moich testach.

 109
Author: jsbueno,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-10 23:38:59

type() jest funkcją, która tworzy klasy (a 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
 69
Author: Eric Lebigot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-28 02:17:29