【问题标题】:Decorating a class method after @property在@property 之后修饰一个类方法
【发布时间】:2011-06-22 20:41:48
【问题描述】:

我想使用装饰器包装除__init__ 之外的各种对象的所有方法。

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

然而,一个属性实例结果的包装等价于:

@my_decorator
@property
def result(self):
    return "Some derived property"

当期望的结果与此等价时:

@property
@my_decorator
def result(self):
    return "Some derived property"

似乎属性对象的属性是只读的,防止在属性包装后修改函数。我已经对所需的hackery 级别不太满意,无论如何我都不想深入研究属性对象。

我能看到的唯一其他解决方案是动态生成一个我希望避免的元类。我错过了什么明显的东西吗?

【问题讨论】:

  • 你有没有察觉到,在这段代码中,当你运行 WrapedObject 的 init 后,一旦它修改了原始类,你就不再有它的非包装版本了吗?跨度>

标签: python metaclass decorator


【解决方案1】:

此示例中还有一些其他问题,但要解决问题,您需要做的就是 是,当你包装一个属性时

当你包装一个属性时,包装它的 __get__ 方法:

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

这是对完成这项工作的代码进行的最简单的修改。 但是,我会将其更改为它正在包装的类的子类,以避免重写其属性。您可以通过简单地使用名称、带有基数的元组和作为参数的字典来以编程方式创建子类。

编辑 - 将代码更改为子类包装类

实际上,对给定类进行子类化几乎不需要对给定代码进行修改, 但对于我指出的type 电话。我刚刚在这里测试过 - 将 WrappedObject 类更改为:

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)

【讨论】:

  • 我同意创建一个子类会更整洁,我宁愿避免猴子修补原始类。
【解决方案2】:

经过一番尝试和错误,我想出了以下解决方案。首先,创建一个模拟装饰描述符的辅助类:

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)

然后,如果你看到一个属性,把它包裹起来:

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

这样工作:

>>> inst = WrappedObject(MyObject)()
>>> print inst.result
Calling decorated function <method-wrapper '__get__' of property object at 0x00BB6930>
Some derived property

那里!没有元类:)

【讨论】:

    【解决方案3】:

    你可以引入“惰性”装饰器,在你自己的装饰器之后应用,例如:

    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)
    

    ..然后使用@lazy_property 而不是@property。 (警告:未经测试的代码,但我希望你明白......)

    【讨论】:

      猜你喜欢
      • 2015-02-14
      • 1970-01-01
      • 1970-01-01
      • 2015-11-22
      • 2021-10-23
      • 2021-12-26
      • 2013-07-09
      相关资源
      最近更新 更多