JEEND0
JEEND0

Reputation: 482

Python (2.7): dynamically adding classmethods

I have some classes whose methods which I want to be able to add to other classes on optionally. My initial solution was to use mixins, but this can get a bit ugly:

class Schedule(Enumerator, Humanizer, Inferer, ...):
    ...

So I thought hey, perhaps I could use class decorators to achieve the same effect.

@enumerator
@humanizer
@inferer
class Schedule(object):
    ...

And here is a sample of the decorator functions:

import inspect

def inferer(original_class):
    from myproj.lib.inferer import Inferer
    methods = inspect.getmembers(Inferer, predicate=inspect.ismethod)
    for method in methods:
        setattr(original_class, method[0], types.MethodTypes(method[1], original_class))
    return original_class

...which seems to add the methods and classmethods appropriately to decorated classes. However, when I call one of these added methods (or classmethods) on the decorated class, I get some errors.

For methods:

>>> Schedule().humanize()
TypeError: unbound method humanize() must be called with Humanizer instance as first argument (got type instance instead)

...which seems to indicate these are added as classmethods?

For classmethods:

>>> schedule = Schedule.infer(foo)
TypeError: infer() takes exactly 2 arguments (3 given)

Note the definition of infer:

class Inferer(object):
    @classmethod
    def infer(cls, dates):
        ...

I added some lines to infer to show what arguments it's getting when called as Schedule.infer():

cls: <class 'myproj.lib.inferer.Inferer'>
dates: <class 'myproj.Schedule'>

So, my question:

What is going wrong in the decorator function to cause these added methods and classmethods to behave strangely? Or, better put, how to I modify the decorator function to handle these additions properly?

Please let me know if I can provide any clarification on any point.

Upvotes: 3

Views: 695

Answers (1)

BenTrofatter
BenTrofatter

Reputation: 2158

Let's say this was a good idea. This is one way you might achieve it. I can't say that I would advise it, though.

def horrible_class_decorator_factory(mixin):
    def decorator(cls):
        d = cls.__dict__.copy()
        bases = tuple([b for b in cls.__bases__ if b != object] + [mixin])
        return type(cls.__name__, bases, d)
    return decorator

Now you can do something like this:

class Inferer(object):
    @classmethod
    def foo(cls):
        return "bar" + cls.__name__

inferer = horrible_class_decorator_factory(Inferer)

@inferer
class X(object):
    pass

X.foo()
"barX"

I'm with the commenters on this one. Just because you can do something doesn't mean you should.

Upvotes: 4

Related Questions