装饰器不应该有副作用吗?

1 投票
2 回答
727 浏览
提问于 2025-04-15 14:26

我在编辑,因为最开始的代码让人困惑。

我本以为这两件事是一样的,

#I would use either of these
#Option 1
def bar(*args):
    pass
foo = deco(bar)

#Option2
@deco
def foo(*args):
    pass

但是如果装饰器deco有副作用,那就不一定了。特别是,我原本期待装饰器是没有副作用的,但我遇到了一个有副作用的装饰器,结果让我吃了亏,

#Option1
def bar(*args):
    pass
foo = register.filter(bar)

#Option 2
@register.filter
def foo(val, arg):
    pass

那么我的期待是错的吗,还是说Django在最佳实践上不一致呢?

2 个回答

0

你的例子在每种情况下表达的意思都不一样!你为什么坚持使用 bar 呢?

看看你的第一个例子:

#Option 1
def bar(*args):
    pass
foo = deco(bar)

#Option2
@deco
def foo(*args):
    pass

选项 1 是(字面意思上)

foo = deco(bar)

但选项 2 相当于

foo = deco(foo)

你看不出其中的区别吗?

所以,总的来说,是的:你的假设和期望都是错的。

如果你需要函数的原始版本和装饰过的版本,可以提前保存原始版本:

def foo(*args):
    pass
bar = foo
foo = deco(foo)
2

其实,这两者是完全一样的:

def foo(*args):
    pass
foo = deco(foo)

@deco
def foo(*args):
    pass

如果你想给 bar 加上装饰器,并把它叫做 foo,那么 foo = deco(bar) 是正确的写法。它的意思是:“给这个之前定义的东西 bar 加上装饰,然后叫它 foo”。装饰器语法的重点在于在定义之前声明这个包装函数,而不是重命名它。

除非你后面还需要用到 bar,否则没有必要用不同的名字来调用未加装饰的函数。这样做会让你失去使用装饰器语法的便利。

deco 不一定要是一个函数。它也可以是一个有 __call__ 方法的对象,这样做的好处是可以很好地封装一些副作用。

撰写回答