4

我很难想到一种好的 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 实例,但我有点想知道是否有合理的方法通过继承来做到这一点。

4

2 回答 2

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 ...
于 2013-01-02T07:30:11.560 回答
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_selfdecorator_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

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

于 2013-01-02T07:45:43.413 回答