3

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.

4
  • 2
    So... the only reason you're using decorators is because the traditional syntax looks ugly? This sounds like an enormous amount of work and additional code fragility just for aesthetics. Commented Nov 11, 2012 at 23:57
  • I'm not sure why you think using decorators like this will be less ugly (problematic) than using mixins. That said you may need to utilize a metaclass to accomplish what you are trying to do. Some years back I recall setting up a system that created classes on the fly from a collection of functions and I used metaclasses for that (don't recall all of the details now, sorry). Commented Nov 11, 2012 at 23:59
  • I agree: the mixins seem simpler and clearer, and they actually work. Why go to all this trouble? Commented Nov 12, 2012 at 0:04
  • Is there any specific reason you need to attach classmethods to the Schedule? Since they are classmethods you could just as well call them directly on e.g. the Inferer class. Commented Nov 12, 2012 at 0:05

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Is there any reason why? All I'm hearing from the commenters, and from you, is that it's a horrible idea and fragile too (thanks for the pointed function name there BTW). Especially helpful.
Python is meant to be easy to read and understand, and once you start messing around with the most basic syntax stuff, you start to introduce the possibility of code brittleness and lack of clarity. It can be really tempting to do things because of your sense of aesthetics. Just remember that the people who designed the language put a lot of thought into it. They've given you a lot of introspective and customizing power, but it's more than enough rope to hang yourself with.
Just thought of a good example of why this is bad. Let's say you've got a few of these mixins, and maybe you've had them inherit from a common base class for some reason. I haven't done any kind of checking on base classes whatsoever. Suddenly, I've introduced method resolution order (MRO) problems. If you'd like to see an example of where this is done effectively, take a look at the Django Model base class. It gets very complicated very quickly.
I appreciate you giving an example as to why it is bad. It is extremely frustrating to be told a technique is horrible without any explanation. I think I've gone in a circle on this whole project by initially trying to mixin dynamically, and based on this "discussion" I will refactor. I'll accept your answer as it does address the question I posed.
Agreed. Compelling reasons are very important. Otherwise, it just feels arbitrary and you don't actually walk away from the issue you were having with any new understanding.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.