Decorating a class method after @property

夙愿已清 提交于 2019-11-30 09:18:11

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.

edit - changing code to subclass wrapped class

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...)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!