将关键字参数传递给类方法装饰器

6 投票
2 回答
1275 浏览
提问于 2025-04-15 20:20

我有一个类,这个类里面有一个叫做output()的方法,这个方法会返回一个matplotlib的图形实例。我写了一个装饰器,它可以把这个图形实例转换成Django的响应对象。

我的装饰器是这样的:

class plot_svg(object):
    def __init__(self, view):
        self.view = view

    def __call__(self, *args, **kwargs):
        print args, kwargs
        fig = self.view(*args, **kwargs)
        canvas=FigureCanvas(fig)
        response=HttpResponse(content_type='image/svg+xml')
        canvas.print_svg(response)
        return response

这是我使用装饰器的方式:

def as_avg(self):
    return plot_svg(self.output)()

我之所以不直接用“@”这种语法,是因为当我用“@”的时候:

@plot_svg
def as_svg(self):
    return self.output()

我会遇到这个错误:

as_svg() takes exactly 1 argument (0 given)

我想通过使用“@”语法来“修复”这个问题,但我不知道怎么才能让它正常工作。我觉得可能是因为self没有在应该传递的地方传递过去……

2 个回答

1

好的,似乎有几个问题。

首先,关于语法。你遇到的“只接受一个参数”的错误,是因为你的装饰器 @plot_svg 期待一个参数(一个视图)。还有其他一些语法错误。

其次,更重要的是,你应该写一个函数,而不是使用装饰器。装饰器会让你失去对某个概念的直接访问,只能得到一个组合后的概念。你其实只需要一个像这样的新函数:

def as_svg(an_object):
    return django_response(an_object.output())

另外,我可能因为代码示例太少而误解了你的问题。

5

没错:当你用一个类来装饰(decorator)时,而不是用一个函数,你需要把它变成一个描述符(descriptor),至少要给它一个 __get__ 方法,这样才能得到“自动的 self”。最简单的办法是直接用函数来装饰,如上面的例子所示:

def plot_svg(view):

    def wrapper(*args, **kwargs):
        print args, kwargs
        fig = view(*args, **kwargs)
        canvas = FigureCanvas(fig)
        response = HttpResponse(content_type='image/svg+xml')
        canvas.print_svg(response)
        return response

    return wrapper

背景:函数之所以在类中定义并通过实例访问时“变成方法”,也就是说,这些函数能自动获取 self,是因为它们是描述符——函数类型有一个 __get__ 方法。

一个类默认是没有 __get__ 方法的,除非你自己加一个。那么为什么不直接用函数来装饰呢?就像上面的例子那样?这样你就能自动得到函数的 __get__ 方法了。而且你会发现嵌套函数的“词法闭包”特性根本不会造成任何问题(实际上,它还简化了事情——嵌套函数调用的是 view,而不是 self.view,如果 self 既可能是你的装饰器类的实例,也可能是你正在装饰的方法所在类的实例,那就会让人很困惑了!)。

撰写回答