2013-01-02 74 views
4

我很难想出一种好的python方法,并且符合oop原则,因为我已经被教会了解如何在python中创建一个相关的方法装饰器族。python中的子类方法装饰器

互不一致的目标似乎是我想能够访问装饰器属性和装饰方法绑定的实例的属性。这就是我的意思是:

from functools import wraps 

class AbstractDecorator(object): 
    """ 
    This seems like the more natural way, but won't work 
    because the instance to which the wrapped function 
    is attached will never be in scope. 
    """ 
    def __new__(cls,f,*args,**kwargs): 
     return wraps(f)(object.__new__(cls,*args,**kwargs)) 

    def __init__(decorator_self, f): 
     decorator_self.f = f 
     decorator_self.punctuation = "..." 

    def __call__(decorator_self, *args, **kwargs): 
     decorator_self.very_important_prep() 
     return decorator_self.f(decorator_self, *args, **kwargs) 

class SillyDecorator(AbstractDecorator): 
    def very_important_prep(decorator_self): 
     print "My apartment was infested with koalas%s"%(decorator_self.punctuation) 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @SillyDecorator 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

if __name__ == "__main__": 
    u = UsefulObject("balloons") 
    u.red() 

这当然产生

My apartment was infested with koalas... 
AttributeError: 'SillyDecorator' object has no attribute 'noun' 

注意,当然总是有办法让这个工作。例如,一个拥有足够参数的工厂会让我将方法附加到一些创建的SillyDecorator实例上,但我有点想知道是否有合理的方法来继承。

回答

2

@miku得到了使用描述符协议的关键思想。这是一个改进,它使装饰器对象与“有用的对象”分离 - 它不会将装饰器信息存储在底层对象上。

class AbstractDecorator(object): 
    """ 
    This seems like the more natural way, but won't work 
    because the instance to which the wrapped function 
    is attached will never be in scope. 
    """ 
    def __new__(cls,f,*args,**kwargs): 
     return wraps(f)(object.__new__(cls,*args,**kwargs)) 

    def __init__(decorator_self, f): 
     decorator_self.f = f 
     decorator_self.punctuation = "..." 

    def __call__(decorator_self, obj_self, *args, **kwargs): 
     decorator_self.very_important_prep() 
     return decorator_self.f(obj_self, *args, **kwargs) 

    def __get__(decorator_self, obj_self, objtype): 
     return functools.partial(decorator_self.__call__, obj_self)  

class SillyDecorator(AbstractDecorator): 
    def very_important_prep(decorator_self): 
     print "My apartment was infested with koalas%s"%(decorator_self.punctuation) 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @SillyDecorator 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

>>> u = UsefulObject("balloons") 
... u.red() 
My apartment was infested with koalas... 
red balloons 

描述符协议是这里的关键,因为它是让你访问都装饰方法,并在其上绑定的对象的东西。在__get__内部,您可以提取有用的对象标识(obj_self)并将其传递给__call__方法。

注意,重要的是要使用functools.partial(或类似机构),而不是简单地存储obj_self作为decorator_self属性。由于装饰方法在类上,因此只存在SillyDecorator的一个实例。你不能使用这个SillyDecorator实例来存储有用的对象实例特定的信息---如果你创建了多个有用的对象并访问它们的装饰方法而不立即调用它们,那将导致奇怪的错误。

但值得指出的是,可能有一个更简单的方法。在你的例子中,你只是在装饰器中存储少量的信息,你不需要稍后改变它。如果是这样,使用装饰器制造者函数可能会更简单:一个函数接受一个参数(或参数)并返回一个装饰器,其行为可以依赖于这些参数。这里有一个例子:

def decoMaker(msg): 
    def deco(func): 
     @wraps(func) 
     def wrapper(*args, **kwargs): 
      print msg 
      return func(*args, **kwargs) 
     return wrapper 
    return deco 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @decoMaker('koalas...') 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

>>> u = UsefulObject("balloons") 
... u.red() 
koalas... 
red balloons 

可以使用decoMaker提前做出装饰后重新使用,如果你不想每次都重新输入信息,你做装饰:

sillyDecorator = decoMaker("Some really long message about koalas that you don't want to type over and over") 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @sillyDecorator 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

>>> u = UsefulObject("balloons") 
... u.red() 
Some really long message about koalas that you don't want to type over and over 
red balloons 

你可以看到,这比为不同类型的decoratorts编写整个类继承树要简单得多。除非你正在编写存储各种内部状态的超级复杂装饰器(这可能会让人困惑),这个装饰器制造者的方法可能是一个更简单的方法。

+0

这太棒了!我认为我需要更好地围绕描述符进行包装,这确实是这样。至于装饰制造商:是的,这就是我最终做的。问题在于,我作为参数传递的是函数;每个版本的装饰器都有多个函数定义。因此,在顶层定义的构造函数方法定义并将它们附加在构造函数中时,感觉有点冒险,因为逻辑上我所做的是非常多的继承。 –

2

改编自http://metapython.blogspot.de/2010/11/python-instance-methods-how-are-they.html。请注意,此变体设置目标实例上的属性,因此,如果没有检查,则可能会覆盖目标实例属性。下面的代码不包含对这种情况的任何检查。

另请注意,此示例显式设置punctuation属性;更普遍的类可以自动发现它的属性。

from types import MethodType 

class AbstractDecorator(object): 
    """Designed to work as function or method decorator """ 
    def __init__(self, function): 
     self.func = function 
     self.punctuation = '...' 
    def __call__(self, *args, **kw): 
     self.setup() 
     return self.func(*args, **kw) 
    def __get__(self, instance, owner): 
     # TODO: protect against 'overwrites' 
     setattr(instance, 'punctuation', self.punctuation) 
     return MethodType(self, instance, owner) 

class SillyDecorator(AbstractDecorator): 
    def setup(self): 
     print('[setup] silly init %s' % self.punctuation) 

class UsefulObject(object): 
    def __init__(self, noun='cat'): 
     self.noun = noun 

    @SillyDecorator 
    def d(self): 
     print('Hello %s %s' % (self.noun, self.punctuation)) 

obj = UsefulObject() 
obj.d() 

# [setup] silly init ... 
# Hello cat ... 
+0

这不完全相同,因为它在实例上设置了属性(因此可能会覆盖该名称的现有属性)。但描述符协议绝对是一种可行的方式。 – BrenBarn

+0

@BrenBarn,是的,这是真的,谢谢你指出。我在帖子中添加了一个简短的注释。 – miku

+0

有可能避免这种限制,请参阅我的答案。 – BrenBarn