how to dynamically instantiate an object inheriting from several classes?

Matimus mccredie at gmail.com
Mon Nov 24 19:10:22 CET 2008


On Nov 21, 2:11 pm, Joe Strout <j... at strout.net> wrote:
> I have a function that takes a reference to a class, and then  
> instantiates that class (and then does several other things with the  
> new instance).  This is easy enough:
>
>     item = cls(self, **itemArgs)
>
> where "cls" is the class reference, and itemArgs is obviously a set of  
> keyword arguments for its __init__ method.
>
> But now I want to generalize this to handle a set of mix-in classes.  
> Normally you use mixins by creating a class that derives from two or  
> more other classes, and then instantiate that custom class.  But in my  
> situation, I don't know ahead of time which mixins might be used and  
> in what combination.  So I'd like to take a list of class references,  
> and instantiate an object that derives from all of them, dynamically.
>
> Is this possible?  If so, how?
>
> Thanks,
> - Joe

I wrote this a while ago. I sort of regret it though. Mixins could
(and I will argue should) be avoided most of the time by delegating to
other objects with less functionality. Utilizing many mixin classes
tends to just make gigantic classes. This is a huge violation of the
"Single Responsibility Principle". That isn't to say that I don't
think there is a place for multiple inheritance. Multiple inheritance
to the point where it is easier to write a metaclass to automatically
generate your __init__ method than it is to write it yourself is a
good indicator that you have gone too far. Which is what I did.

code:

import inspect

class AutoGenInitMetaError(Exception):
    """ Exception is raised if AutoGenInitMeta cannot auto-generate a
    constructor for a class because of conflicting parameters in base
classes.
    """
    pass

class AutoGenInitMeta(type):
    """ Meta-Class for automatically generating __init__ method for a
class
    with multiple mixin base classes.
    """
    def __new__(cls, name, bases, assoc):
        if "__init__" in assoc:
            return super(AutoGenInitMeta, cls).__new__(cls, name,
bases, assoc)
        args = ['self']
        dargs = []
        defaults = []
        tmpl = []
        varg = None
        vkwarg = None
        tmpl = ["def __init__%s:\n"]
        for base in bases[::-1]:
            a, va, vkw, d = argspec = inspect.getargspec
(base.__init__)
            argspecfmt = inspect.formatargspec(*argspec[:3])
            if d:
                num_d = len(d)
                args += a[1:-num_d]
                defaults += d
                dargs += a[-num_d:]
            else:
                # remember to stip off self
                args += a[1:]
            if va is not None:
                if varg is not None:
                    raise AutoGenInitMetaError(
                        "There must be only one `*` arg in base
constructors"
                    )
                varg = va

            if vkw is not None:
                if vkwarg is not None:
                    raise AutoGenInitMetaError(
                        "There must be only one `**` arg in base
constructors"
                    )
                vkwarg = vkw
            tmpl.append("    %s.__init__%s\n"%(base.__name__,
argspecfmt))
        tmpl = "".join(tmpl)
        argspec = (args + dargs, varg, vkwarg, defaults)
        exec tmpl%inspect.formatargspec(*argspec) in globals(), assoc

        return super(AutoGenInitMeta, cls).__new__(cls, name, bases,
assoc)


How do you use it?

>>> class C(object):
...     def __init__(self, a, b):
...         self.a = a
...         self.b = b
...
>>> class D(object):
...     def __init__(self, c, d):
...         self.c = c
...         self.d = d
...
>>> class CD(C, D):
...     __metaclass__ = AutoGenInitMeta
...
>>>
>>> x = CD(1,2,3,4)
>>>
>>> x.a
3
>>> x.b
4
>>> x.c
1
>>> x.d
2
>>>

Notice that the arguments to D came before C. So you have to list the
classes in reverse order of how you want the arguments listed.

I post it as an example of a "neat python trick". Even the "neat"
might be self indulgence. I encourage anybody tempted to use this to
refactor/redesign instead.

Matt



More information about the Python-list mailing list