在Python装饰器中丢失变量作用域
这个简单的装饰器按预期工作:
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 个回答
我之前也遇到过这个问题,原因是你不能设置一个放在闭包里的变量。不知道你是不是也遇到这种情况,但知道这一点是有帮助的。
我想我明白发生了什么,让我来解释一下:
这跟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上提问题的话。