I want to wrap every method of various objects except __init__
using a decorator.
class MyObject(object):
def method(self):
print "method called on %s" % str(self)
@property
def result(self):
return "Some derived property"
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) or isinstance(item, property)):
setattr(cls, attr, my_decorator(item))
self._cls = cls
def __call__(self, *args, **kwargs):
return self._cls(*args, **kwargs)
inst = WrappedObject(MyObject)()
However, the wrapping of a property instance results is equivalent to this:
@my_decorator
@property
def result(self):
return "Some derived property"
When the desired result is something equivalent to this:
@property
@my_decorator
def result(self):
return "Some derived property"
It seems the attributes of a property object are read-only preventing modifying the function after property has wrapped it. I'm not too comfortable with the level of hackery required already and I'd rather not delve into the property object anyway.
The only other solution I can see is to generate a metaclass on the fly which I was hoping to avoid. Am I missing something obvious?
To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function. Here, when we decorate, multiply_together with integer_check, the integer function gets called.
In Python, the @classmethod decorator is used to declare a method in the class as a class method that can be called using ClassName. MethodName() . The class method can also be called using an object of the class. The @classmethod is an alternative of the classmethod() function.
The @property is a built-in decorator for the property() function in Python. It is used to give "special" functionality to certain methods to make them act as getters, setters, or deleters when we define properties in a class.
To decorate a function with a class, we must use the @syntax followed by our class name above the function definition. Following convention, we will use camel-case for our class name. In the class definition, we define two methods: the init constructor and the magic (or dunder) call method.
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 :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With