如何装饰可调用类的实例?

6 投票
2 回答
2078 浏览
提问于 2025-04-18 09:08
def decorator(fn):
    def wrapper(*args, **kwargs):
        print 'With sour cream and chives!',
        return fn(*args, **kwargs)
    return wrapper

class Potato(object):
    def __call__(self):
        print 'Potato @ {} called'.format(id(self))

spud = Potato()
fancy_spud = decorator(Potato())

这段代码展示了两个可调用的类实例,一个是用装饰器装饰过的,另一个是普通的:

>>> spud()
Potato @ 140408136280592 called
>>> fancy_spud()
With sour cream and chives! Potato @ 140408134310864 called

我在想,是否可以只对某一个实例使用@decorator这种语法,而不是对整个类或方法进行装饰,这样会影响到每一个实例。根据这个流行的回答,@语法其实只是个简化写法:

function = decorator(function)

但这样说是不是太简单化了?我尝试了很多方法,发现只有在defclass、空格或者@another_decorator之前使用这种语法才有效。

@decorator
baked = Potato()

这是一个SyntaxError错误。

baked = Potato()
@decorator
baked

这也是SyntaxError错误。

@decorator
def baked(_spud=Potato()):
    return _spud()

这样写可以,但看起来很丑,而且有点像是在作弊。

2 个回答

5

是的,这个说法有点过于简单化了。如果我们看看语法规则,会发现decorator这个词只出现在decorators的规则里,而这个规则只在classdef(类定义)或funcdef(函数定义)中出现:

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
decorators: decorator+
decorated: decorators (classdef | funcdef)

语言参考文档中提到的内容(我觉得这也是链接回答中重复的内容)是

@f1(arg)
@f2
def func(): pass

等同于

def func(): pass
func = f1(arg)(f2(func))

对于类定义也是类似的。但是,这并不意味着@decorator的语法可以随便用;它只能在函数或类定义的前面使用。

顺便提一下,甚至官方文档也不是完全正确的;在调用装饰器的时候,函数(或类)并没有绑定到外部的命名空间或作用域中,所以这些语法并不完全等价。

关于defclass语句,有一些有趣的地方,我认为这也是它们是唯一支持@decorator语法的原因之一:它们是Python中唯一可以将名称绑定到一个“知道这个名称是什么”的对象上的方式。

最后,这里还有另一种你可能喜欢的调用装饰器的方法:

@decorator
class baked:
    __metaclass__ = lambda *_: Potato()
3

你的问题是:

根据这个热门的回答,@语法其实就是一个简化写法,等同于:

function = decorator(function)

不过,更准确的说法是:

@decorator
def function():
    pass

其实是另一种简化写法:

def function():
    pass
function = decorator(function)

装饰器是专门用来装饰函数、方法或类的定义的。关于类装饰器的介绍,可以参考这个PEP文档,里面描述了语法规则:

decorated: decorators (classdef | funcdef)

funcdef: 'def' NAME parameters ['->' test] ':' suite

正如你所看到的,装饰器必须紧接在classdeffuncdef之前,所以不能直接在可调用类的实例上使用它。

撰写回答