python中的子类化方法装饰器

时间:2013-01-02 07:01:35

标签: python decorator

我很难想出一种优秀的python方式,并且与oop原则一致,因为我已经被教导要弄清楚如何在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 个答案:

答案 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 ...

答案 1 :(得分: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实例来存储有用的特定于对象实例的信息 - 如果你创建了多个UsefulObjects并且在没有立即调用它们的情况下访问它们的装饰方法,这将导致奇怪的错误。

值得指出的是,可能有一种更简单的方法。在您的示例中,您只在装饰器中存储少量信息,以后不需要更改它。如果是这种情况,那么使用一个decorator-maker函数可能更简单:一个接受一个参数(或参数)并返回一个装饰器的函数,它的行为可以依赖于那些参数。这是一个例子:

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编写一个完整的类继承树要简单得多。除非你正在编写存储各种内部状态的超级复杂装饰器(这可能会让人感到困惑),否则这种装饰器制造方法可能是一种更容易的方法。