在Python中实现装饰器模式

时间:2010-06-25 14:45:34

标签: python design-patterns

我想在Python中实现decorator pattern,我想知道是否有办法编写一个装饰器来实现它想要修改的函数,而不需要为所有函数编写样板转发到装饰对象。像这样:

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

7 个答案:

答案 0 :(得分:32)

您可以使用__getattr__

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

答案 1 :(得分:9)

作为菲利普回答的附录;如果您不仅需要修饰,而且还要保留对象的类型,Python允许您在运行时子类化实例:

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)

这比你的例子所涉及的要多得多,你知道提前装饰的课程。

这个可能就足够了:

class foo_decorator(foo):
    def __init__(self, decoratee):
        self.__dict__.update(decoratee.__dict__)

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

答案 2 :(得分:2)

这可能不是最好的做法,但你可以为实例添加功能,就像我帮助将代码从Django的ORM转换到SQLAlachemy一样,如下所示:

def _save(self):
    session.add(self)
    session.commit()
setattr(Base,'save',_save)

答案 3 :(得分:2)

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

如果您遵循“装饰器模式”,则装饰器类派生自基础装饰类。 (在UML图中,缺少从WindowDecorator到Window的继承箭头。)

class foo_decorator(foo):

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

BTW:在强类型语言中还有另外一个原因,为什么装饰器必须从装饰类派生:否则你将无法链接装饰器。

答案 4 :(得分:0)

补充@Alec Thomas回复。我修改了他的答案以遵循装饰模式。这样你就不需要提前知道你正在装修的课程。

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

然后,您可以将其用作:

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

答案 5 :(得分:0)

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

用例是:

  • 我有一个带有方法A和B的对象X.
  • 我创建一个覆盖A的装饰器类Y。
  • 如果我实例化Y(X)并调用A,它将按预期使用装饰的A.
  • 如果B调用A,那么如果我实例化Y(X)并在装饰器上调用B,则来自B内的调用将转到原始对象上的旧A,这是不合需要的。我希望老B也能打电话给新的A.

可以像这样达到这种行为:

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方法之外的所有其他内容。

代码在装饰对象中查找请求的属性,如果它是一个方法(现在不对属性起作用,但是支持它们的更改不应该太困难),代码然后拉出方法中的实际函数和使用描述符接口调用它" rebinds"作为方法的函数,但在装饰器上。然后它被返回并且很可能被执行。

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

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

答案 6 :(得分:0)

在Python 3中,Philipp接受的答案提出了RuntimeError: maximum recursion depth exceeded

对我有效的方式:

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

    def f2(self):
        print("original f2")

class FooDecorator(object):
    def __init__(self, decoratee):
        self._decoratee = decoratee

    def f1(self):
        print("decorated f1")
        return self._decoratee.f1()

    def __getattr__(self, name):
        if name in ['f1', '_decoratee']:
            raise AttributeError()
        return getattr(self._decoratee, name)

f = FooDecorator(Foo())
f.f1()
# decorated f1
# original f1
f.f2()
# original f2

此变通办法的灵感来自Ned Batchelder's blog