Reputation: 482
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
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