Decorating a class method after @property

前端 未结 3 1737
深忆病人
深忆病人 2020-12-16 04:05

I want to wrap every method of various objects except __init__ using a decorator.

class MyObject(object):

    def method(self):
        print \         


        
相关标签:
3条回答
  • 2020-12-16 04:44

    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)
    
    0 讨论(0)
  • 2020-12-16 04:51

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

    0 讨论(0)
  • 2020-12-16 04:57

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

    0 讨论(0)
提交回复
热议问题