在Python中实现装饰器模式

Implementing the decorator pattern in Python

我想在python中实现decorator模式,我想知道是否有一种方法可以编写一个只实现它想要修改的函数的decorator,而不需要为所有转发到被修饰对象的函数编写Boiler Plate。就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class foo(object):
    def f1(self):
        print"original f1"
    def f2(self):
        print"original f2"

class foo_decorator(object):
    def __init__(self, decoratee):
        self._decoratee = decoratee
    def f1(self):
        print"decorated f1"
        self._decoratee.f1()
    def f2(self):              # I would like to leave that part out
        self._decoratee.f2()

我想把对foo_decorator.f2的呼叫自动转发给decoratee.f2。有没有一种方法可以编写一个将所有未实现的函数调用转发给decoratee的通用方法?


您可以使用__getattr__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class foo(object):
    def f1(self):
        print"original f1"
    def f2(self):
        print"original f2"

class foo_decorator(object):
    def __init__(self, decoratee):
        self._decoratee = decoratee
    def f1(self):
        print"decorated f1"
        self._decoratee.f1()
    def __getattr__(self, name):
        return getattr(self._decoratee, name)

u = foo()
v = foo_decorator(u)
v.f1()
v.f2()


作为Philipp答案的补充;如果您不仅需要修饰,而且需要保留对象的类型,python允许您在运行时对实例进行子类化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class foo(object):
    def f1(self):
        print"original f1"

    def f2(self):
        print"original f2"


class foo_decorator(object):
    def __new__(cls, decoratee):
        cls = type('decorated',
                   (foo_decorator, decoratee.__class__),
                   decoratee.__dict__)
        return object.__new__(cls)

    def f1(self):
        print"decorated f1"
        super(foo_decorator, self).f1()


u = foo()
v = foo_decorator(u)
v.f1()
v.f2()
print 'isinstance(v, foo) ==', isinstance(v, foo)

对于您的示例来说,这比严格要求要复杂一些,因为您知道该类是提前装饰的。

这可能就足够了:

1
2
3
4
5
6
7
class foo_decorator(foo):
    def __init__(self, decoratee):
        self.__dict__.update(decoratee.__dict__)

    def f1(self):
        print"decorated f1"
        super(foo_decorator, self).f1()


链接的维基百科文章中的UML图是错误的,您的代码也是错误的。

如果遵循"decorator模式",则decorator类是从基本decorated类派生的。(在UML图中,缺少从WindowDecorator到Window的继承箭头)。

具有

1
class foo_decorator(foo):

您不需要实现未修饰的方法。

顺便说一句:在强类型语言中,还有一个原因,就是为什么修饰器必须从修饰类派生:否则您将无法链接修饰器。


可以说,这不是最佳实践,但您可以向实例添加功能,正如我所做的,以帮助将代码从Django的ORM转换为sqalachemy,如下所示:

1
2
3
4
def _save(self):
    session.add(self)
    session.commit()
setattr(Base,'save',_save)


在我的一个项目中,我还需要做一件特别的事情,即即使底层对象也应该实际执行在decorator中重新实现的方法。如果你知道目标在哪里,这实际上是很容易做到的。

用例是:

  • 我有一个带有方法A和B的对象X。
  • 我创建了一个覆盖a的decorator类y。
  • 如果我实例化y(x)并调用a,它将按预期使用修饰a。
  • 如果b调用a,那么如果我实例化y(x)并在decorator上调用b,那么从b内部调用将转到原始对象上的旧a,这是不需要的。我也希望老B给新A打电话。
  • 小精灵

    这样的行为是可能的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import inspect
    import six      # for handling 2-3 compatibility

    class MyBaseDecorator(object):
        def __init__(self, decorated):
            self.decorated = decorated

        def __getattr__(self, attr):
           value = getattr(self.decorated, attr)
           if inspect.ismethod(value):
               function = six.get_method_function(value)
               value = function.__get__(self, type(self))
           return value

    class SomeObject(object):
        def a(self):
            pass

        def b(self):
            pass

    class MyDecorator(MyBaseDecorator):
        def a(self):
            pass

    decorated = MyDecorator(SomeObject())

    当我输入除getattr方法之外的其他所有东西时,这个方法可能不适用。

    代码在修饰对象中查找请求的属性,如果它是一个方法(现在对属性不起作用,但是为支持它们所做的更改不应该太困难),那么代码将实际函数从方法中提取出来,并使用描述符接口调用将函数"重新绑定"为方法,但在修饰器上。然后返回并很可能执行。

    这样做的效果是,如果b曾经对原始对象调用a,那么当您对对象进行了装饰,并且有来自装饰器的任何方法调用时,装饰器会确保访问的所有方法都绑定到装饰器,因此使用装饰器而不是原始对象查找对象。因此,装饰器中指定的方法优先。

    P.S.:是的,我知道它看起来很像继承,但这是在多个对象的组合意义上完成的。


    补充@alec thomas回复。我修改了他的答案,以遵循装饰师的模式。这样你就不需要提前知道你要装饰的班级了。

    1
    2
    3
    4
    5
    6
    class Decorator(object):
        def __new__(cls, decoratee):
            cls = type('decorated',
                       (cls, decoratee.__class__),
                       decoratee.__dict__)
            return object.__new__(cls)

    然后,您可以将其用作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class SpecificDecorator(Decorator):
        def f1(self):
            print"decorated f1"
            super(foo_decorator, self).f1()

    class Decorated(object):
        def f1(self):
            print"original f1"


    d = SpecificDecorator(Decorated())
    d.f1()