I want to wrap every method of various objects except __init__
using a decorator.
class MyObject(object):
def method(self):
print \
There are a few other issues in this sample, but to atain to question, all you have to do is, when you are wrapping a property
When you are wrapping a property, wrap its __get__ method instead:
class MyObject(object):
def method(self):
print "method called on %s" % str(self)
@property
def result(self):
return "Some derived property"
def common(self, a=None):
print self
def my_decorator(func):
def _wrapped(*args, **kwargs):
print "Calling decorated function %s" % func
return func(*args, **kwargs)
return _wrapped
class WrappedObject(object):
def __init__(self, cls):
for attr, item in cls.__dict__.items():
if attr != '__init__' and callable(item):
setattr(cls, attr, my_decorator(item))
elif isinstance(item, property):
new_property = property(my_decorator(item.__get__), item.__set__, item.__delattr__)
setattr(cls, attr, new_property)
self._cls = cls
def __call__(self, *args, **kwargs):
return self._cls(*args, **kwargs)
inst = WrappedObject(MyObject)()
That is the simpelst modification to your code that does the job. I'd however change it to dinamically a subclass of the classs it is wrapping, in order to avoid re-writing its attributes. You can create a subclass programtically by simply caling type with the name, a tuple withe the bases, and a dict as parameters.
Actually, subclassing the given class requires almost no modification on the given code,
but for the type
call I indicated. I just tested it here - change your WrappedObject class to:
class WrappedObject(object):
def __init__(self, cls):
dct = cls.__dict__.copy()
for attr, item in dct.items():
if attr != '__init__' and callable(item):
dct[attr] = my_decorator(item)
elif isinstance(item, property):
new_property = property(my_decorator(item.__get__), item.__set__, item.__delattr__)
dct[attr] = new_property
self._cls = type("wrapped_" + cls.__name__, (cls,), dct)
def __call__(self, *args, **kwargs):
return self._cls(*args, **kwargs)
After a bit of try-and-error, I came up with the following solution. First, create a helper class that will emulate a decorated descriptor:
class DecoratedDescriptor(object):
def __init__(self, descriptor, decorator):
self.funcs = {}
for attrname in '__get__', '__set__', '__delete__':
self.funcs[attrname] = decorator(getattr(descriptor, attrname))
def __get__(self, *args, **kwargs):
return self.funcs['__get__'](*args, **kwargs)
def __set__(self, *args, **kwargs):
return self.funcs['__set__'](*args, **kwargs)
def __delete__(self, *args, **kwargs):
return self.funcs['__delete__'](*args, **kwargs)
Then, if you see a property, wrap it in it:
# Fragment of your WrappedObject.__init__ method:
if attr != '__init__' and callable(item):
setattr(cls, attr, my_decorator(item))
elif isinstance(item, property):
setattr(cls, attr, DecoratedDescriptor(item, my_decorator))
This works like this:
>>> inst = WrappedObject(MyObject)()
>>> print inst.result
Calling decorated function <method-wrapper '__get__' of property object at 0x00BB6930>
Some derived property
There! No metaclasses :)
You could introduce "lazy" decorators, which are applied after your own decorator, for example:
class Lazy(object):
def __init__(self, decorator):
self.decorator = decorator
def __call__(self, method):
self.method = method
return self
def my_decorator(func):
def _wrapped(*args, **kwargs):
print "Calling decorated function %s" % func
return func(*args, **kwargs)
if isinstance(func, Lazy):
lazy = func
func = lazy.method
return lazy.decorator(_wrapped)
return _wrapped
lazy_property = Lazy(property)
..and then use @lazy_property
instead of @property
. (Warning: untested code, but I hope you get the idea...)