pythonclassinheritance

How can I dynamically create derived classes from a base class


For example I have a base class as follows:

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

From this class I derive several other classes, e.g.

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

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

Is there a nice, pythonic way to create those classes dynamically by a function call that puts the new class into my current scope, like:

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

As there will be comments and questions why I need this: The derived classes all have the exact same internal structure with the difference, that the constructor takes a number of previously undefined arguments. So, for example, MyClass takes the keywords a while the constructor of class TestClass takes b and c.

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

But they should NEVER use the type of the class as input argument like

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

I got this to work but would prefer the other way, i.e. dynamically created class objects.


Solution

  • This bit of code allows you to create new classes with dynamic names and parameter names. The parameter verification in __init__ just does not allow unknown parameters, if you need other verifications, like type, or that they are mandatory, just add the logic there:

    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
    

    And this works like this, for example:

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

    I see you are asking for inserting the dynamic names in the naming scope -- now, that is not considered a good practice in Python - you either have variable names, known at coding time, or data - and names learned in runtime are more "data" than "variables" -

    So, you could just add your classes to a dictionary and use them from there:

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

    And if your design absolutely needs the names to come in scope, just do the same, but use the dictionary returned by the globals() call instead of an arbitrary dictionary:

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

    (It indeed would be possible for the class factory function to insert the name dynamically on the global scope of the caller - but that is even worse practice, and is not compatible across Python implementations. The way to do that would be to get the caller's execution frame, through sys._getframe(1) and setting the class name in the frame's global dictionary in its f_globals attribute).

    update, tl;dr: This answer had become popular, still its very specific to the question body. The general answer on how to "dynamically create derived classes from a base class" in Python is a simple call to type passing the new class name, a tuple with the baseclass(es) and the __dict__ body for the new class -like this:

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

    update
    Anyone needing this should also check the dill project - it claims to be able to pickle and unpickle classes just like pickle does to ordinary objects, and had lived to it in some of my tests.