在Python装饰器中丢失变量作用域

1 投票
2 回答
1501 浏览
提问于 2025-04-16 12:25

这个简单的装饰器按预期工作:

def protect(*permissions):
    def outer(f):
        def inner(*args):
            print permissions[0]
            return f(*args)  
        return inner  
    return outer

@protect('protected')
def func(var):
    return var

print func('something')

输出结果是:

protected
something

但是,当我把这个装饰器放到我的项目中使用时,出现了一些奇怪的情况:在inner函数里面,permissions这个变量没有定义。

我在想,可能是我对Python的变量作用域或者装饰器的一些细节不太了解,这可能导致了这个问题。任何建议都很欢迎。

2 个回答

0

我之前也遇到过这个问题,原因是你不能设置一个放在闭包里的变量。不知道你是不是也遇到这种情况,但知道这一点是有帮助的。

1

我想我明白发生了什么,让我来解释一下:

这跟Python在“内层”函数外面看不到“permissions”这个变量有关。因为当“内层”函数被定义的时候,“permissions”早就已经在最外层的作用域中定义了。所以在编译inner的时候,这个变量被当作全局变量来处理。(这就是为什么需要准确的错误信息——NameError可能是因为局部变量在定义之前被使用,或者是因为全局变量不存在——准确的错误信息在这种情况下能提供很多帮助)

换句话说,你很可能遇到了一个实现上的bug——请尽量提供导致这个问题的最小代码量,以及你使用的Python版本。如果可以的话,试试你能找到的最新小版本,然后就可以去bugs.python.org提个问题了。

我看到有两个解决办法——第一个是一个小技巧,可以确认我的判断,但可能根本不管用:在outer函数中,inner函数体外部访问一下permissions变量,这样解释器就会把它当作外部的非局部变量来处理,并传递到内层函数中。

另一个解决办法更稳妥,也更符合代码风格:在这种情况下,使用一个类作为装饰器,而不是依赖多个嵌套函数及其闭包。

上面的代码可以改写成:

class Protect(object):
    def __init__(self, *permissions):
        self.permissions = permissions
    def __call__(self, f):
        def inner(*args):
            print self.permissions[0]
            return f(*args)  
        return inner  

@Protect('protected')
def func(var):
    return var

print func('something')

这段代码不依赖嵌套闭包,从而避免了你遇到的bug。此外,它遵循了“扁平结构优于嵌套结构”和“显式优于隐式”的编码原则。

不过,请大家帮忙追踪这个bug,给我们提供你的版本和实际触发这个行为的代码,如果不想在python.org上提问题的话。

撰写回答