2010-06-25 112 views
21

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

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

我想有转发给decoratee.f2自动foo_decorator.f2电话。有没有办法编写一个将所有未实现函数调用转发到decoratee的泛型方法?

+1

你可以举一个简单的子类化不会起作用的例子吗?你也可以动态地进行子类化 - 这种模式看起来像是一种解决不了那些不支持多重继承的语言的解决方法。 – 2010-06-25 15:26:45

+0

我想在运行时装饰对象。我想将不同的装饰器应用于一个对象,并能够再次移除它们。子类创建后不能更改它,或者它可以吗? – 2010-06-25 15:50:55

+0

如果你有类'A'并改变'A',即增加一个新方法,'A.foo = lambda self:self',这将反映在A ..的所有实例上,因为* everything *是在运行时确定的。伟大的方式来产生绝对不可维护的代码。 – 2010-06-25 17:01:02

回答

30

您可以使用__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

这是一个好主意。但是,应该注意的是,如果您使用抽象基类(即导入abc并使用__metaclass__ = abc.ABCMeta来定义抽象方法和属性),那么它将不起作用。 – abergou 2012-02-23 18:28:12

+0

这对__str__等函数不起作用。那里发生了什么? – 2016-05-02 15:22:19

+0

@JohnKitchin:重定向不适用于双下划线方法,因为这些必须(出于性能原因)在类上定义,而不是在实例上定义。 [(更多信息)](https://docs.python.org/3/reference/datamodel.html#special-lookup)我相信唯一的解决方法是显式定义和重定向像'__str__'这样的方法。 – 2016-09-05 09:28:27

2

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

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

我也考虑过这个,但对我来说感觉很不对劲。也许这是因为我来自C++? – 2010-06-25 14:56:25

+2

对此感觉错误是公平的。它可以被认为是猴子补丁。然而,它在很少代码的情况下运行良好,当一切都是动态的和通过引用的时候,它是一个非常不同的世界,有更多的可能性。 – andyortlieb 2010-06-25 15:16:28

+0

我喜欢它。对于这个例子,其他任何东西都会引入更复杂的IMO。 – Chomeh 2015-03-06 02:03:07

1

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

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

class foo_decorator(foo): 

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

顺便说一句:在强类型语言中,还有一个原因,为什么装饰器必须从装饰类派生:否则你不能链装饰器。

+0

UML图显示了基类的继承和聚合(装饰器模式的两个基本部分)。您从基类继承,因此您看起来像原始类型,并且您持有对基类实例的引用,您可以在其中隔离对基类的访问。维基百科文章在步骤1和2中说明了这一点:“(1)子类化原始组件,(2)将组件指针添加为字段”。所以最初的问题实际上是关于Python鸭子打字,关于装饰者模式,也不是Python装饰者! – maxpolk 2014-12-26 19:51:31

8

作为菲利普答案的附录;如果你不仅需要装修,但保留对象的类型,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() 
0

为了补充@Alec托马斯答复。我修改了他的答案以遵循装饰器模式。这样你就不需要事先知道你正在装修的课程。

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

在我的项目之一,我也需要做一个特别的事情,那就是即使是底层对象应实际执行的方法在装饰器中被重新实现。如果你知道它的目标是什么,它确实很容易做到。

用例是:

  • 我有方法A和B.对象X
  • 我创建一个装饰类Y来覆盖A.
  • 如果我实例化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方法一切除了我的头顶。

代码查找装饰对象中的请求属性,如果它是一个方法(现在不适用于属性,但支持它们的更改不应太困难),则代码将实际函数超出了方法,并使用描述符接口调用,它将函数作为方法“重新绑定”,但在装饰器上。然后返回并最有可能执行。

这样做的效果是,如果b有史以来最原始的对象上调用a,那么当你有装饰物并没有任何方法调用从装饰来临,装饰可以确保访问的所有方法都绑定到而是使用装饰器而不是原始对象查找事物,因此装饰器中指定的方法优先。

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