如何在不复制整个方法的情况下将装饰器添加到 Python 中的继承方法?

Posted

技术标签:

【中文标题】如何在不复制整个方法的情况下将装饰器添加到 Python 中的继承方法?【英文标题】:How to add a decorator to inherited method in Python without copying whole method? 【发布时间】:2015-07-10 22:19:59 【问题描述】:

我有一个从其父类继承修饰属性的类。我想再添加一个装饰器(@thedecorator1("yyy")),但不会覆盖整个方法和现有的装饰器。 (decorator的顺序在某种程度上很重要,decorator1应该总是在property.setter和thedecorator2之间)

这在 Python 3 中可行吗?

from functools import wraps

class thedecorator1:
    def __init__ (self, idx):
        self.idx = idx
    def __call__ (self, func):
        @wraps(func)
        def wrapped(*args, **kwargs):
            print("thedecorator1", self.idx, args)
            return func(*args, **kwargs)
        return wrapped

def thedecorator2(func):
    def _thedecorator2(self, *args, **kwargs):
        print("thedecorator2",args)
        return func(self, *args, **kwargs)
    return _thedecorator2

class SomeClass:
    """That's te base class"""
    _some_property = None

    @property
    @thedecorator2
    def someproperty(self):
        return self._some_property

    @someproperty.setter
    @thedecorator1("xxx")
    @thedecorator2
    def someproperty(self, value):
        self._some_property = value  

class AnotherClass(SomeClass):
    """That's what works, but I need to copy everything everytime"""

    _some_property = None

    @property
    @thedecorator2
    def someproperty(self):
        return self._some_property

    @someproperty.setter
    @thedecorator1("yyy")
    @thedecorator1("xxx")
    @thedecorator2
    def someproperty(self, value):
        self._someproperty = value

class YetAnotherClass(SomeClass):
    """That's what I think I need"""

    dosomethingsmart(target = someproperty, decorator =  thedecorator1("yyy"), after=someproperty.setter)

【问题讨论】:

请注意,这是有效的 Python3 - 但在 Python2.x 中不起作用,因为您的 thedecorator1 类不继承自对象 - 使其成为“旧样式类”。许多不错的类机制在旧式类中不起作用 - 描述符,它是属性成为一个的基础。您的“财产”声明在那里无效。 【参考方案1】:

考虑这个程序:

from functools import wraps

def dec1(fn):
    @wraps(fn)
    def wrap(*args, **kw):
        print "dec1", fn.__name__
        return fn(*args, **kw)
    return wrap

def dec2(fn):
    @wraps(fn)
    def wrap(*args, **kw):
        print "dec2", fn.__name__
        return fn(*args, **kw)
    return wrap

class C(object):
    prop = None
    @property
    def foo(self):
        return self.prop

    @foo.setter
    @dec1
    def foo(self, x):
        self.prop = x

class D(C):
    foo = property(
        C.foo.fget,
        dec2(C.foo.fset),
        C.foo.fdel)

print '.'
c=C()
c.foo = 7
print '.'
d = D()
d.foo = 8
print '.'

D.foo 是一个从 C.foo 继承 get 和 delete 函数的属性,但包装了 set 函数。

D 的另一种定义是:

class D(C):
    foo = C.foo.setter(dec2(C.foo.fset))

无论哪种情况,程序的输出都是:

.
dec1 foo
.
dec2 foo
dec1 foo
.

请注意,对c.foo 的赋值调用一个包装器,而对d.foo 的赋值调用两者。

【讨论】:

【参考方案2】:

没有办法做到这一点,因为装饰器不“知道”在它之前或之后应用的任何其他装饰器。装饰器不是附加到函数的额外信息;相反,当您使用装饰器时,它会将原始函数替换为装饰版本。这不像你有一个包含单个装饰器列表的函数。您只是得到应用 所有 装饰器的最终结果,并被压缩到一个对象中,并且您以后无法窥视内部并知道哪些装饰器有效。所以当你写这个时:

class SomeClass:
    """That's te base class"""
    _some_property = None

    @property
    @thedecorator2
    def someproperty(self):
        return self._some_property

现在SomeClass.someproperty 是一个属性对象。该属性对象包装了thedecorator2,但propertythedecorator2一无所知;它只是包装给定的对象。您定义了一个名为someproperty 的方法,但装饰器将其包装并“埋葬”了原始函数;没有一般的方法可以“展开”一系列装饰器以访问原始函数或部分装饰的中间函数(例如,应用 thedecorator2 而不是 property 的结果)。

稍后当你定义另一个类时,你说你想“添加一个装饰器”。但是为了什么?您唯一可以访问的是SomeClass.someproperty。您定义的原始功能不可用;它被“埋”在装饰器下,你无法将其取出。

现在,对于 一些 装饰器,您也许可以恢复原来的功能。例如,property 将其 getter 函数存储在其 fget 属性中。但是装饰器如何(或者如果!)存储原始功能取决于该装饰器。例如,您的 thedecorator2 装饰器不会以任何方式暴露 func。因此,如果AnotherClass 确切地知道最初应用了哪些装饰器,它可能能够使用关于每个装饰器的特定知识来展开它们。但这仍然需要您知道最初应用了哪些装饰器,因此不必在 AnotherClass 中复制该信息,这不会为您节省任何事情。

底线是装饰者不会“保存”他们装饰的原始东西;他们只是把它压成他们生产的任何东西。没有通用的方法可以“剥离”已装饰的函数的装饰器,甚至无法知道应用了哪些装饰器。因此你不能有insert_new_decorator(somefunction, after=somedecorator)这样的东西,因为没有办法剥离到装饰层来知道somedecorator在堆栈中的位置。

装饰师不像衣服,你可以脱下外套,在里面穿上另一件衬衫,然后再穿上原来的外套。相反,想象一下草莓蘸巧克力,然后蘸生奶油,然后淋上糖霜。你不能“打开”奶油和糖霜,然后在下面涂上不同的巧克力涂层。整个东西都融合成一个无法拆开的物体。

【讨论】:

很好的答案——也许你可以添加一个段落说明如果在子类中添加的装饰器总是可以是最外层的(当然,不包括 propertyalready 的方法)它是可行的,只需要 使用装饰器语法,而是在类主体上写一些东西:some_ancestor_method = newdecorator(SomeClass.__dict__["some_ancestor_method"])

以上是关于如何在不复制整个方法的情况下将装饰器添加到 Python 中的继承方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不复制目标 NSManagedObject 的情况下将目标 NSManagedObject 添加到另一个具有反向多对多核心数据关系的对象?

如何在不破坏滚动功能的情况下将 UIImage 添加到 UIScrollView?

SSAS - 是不是可以在不重新部署整个多维数据集的情况下将属性添加到现有维度

如何在不复制 C# 的情况下将结构数组元素提取到变量中?

如何在不使用 Xcode 的情况下将 iCloud Documents 功能添加到我的应用程序?

装饰器模式