最近碰到这么个问题,有这么个函数,用来将HTML转义字符变回原来的字符:

其中unichr用来将一个整数转换成Unicode字符,仅在Python2中存在。Python3中,chr可以同时处理ASCII字符和Unicode字符。所以我们在Python3环境中将unichr映射到chr上。

运行这段代码会在第8行报错:NameError: free variable ‘unichr’ referenced before assignment in enclosing scope。而且只有Python2会报错,Python3不会。

首先从问题上看,报错的原因是在闭包replc里unichr没有定义。

但是Python2明明是有unichr这个内置函数的,为啥就变成未定义呢?

为了搞清楚问题,我们用了一个最小化的测试用例:

运行到print那行报错“UnboundLocalError: local variable ‘d’ referenced before assignment”。我们注意到这时报错的是local variable没有定义。明明a之前是一个全局变量而且if根本不会执行啊。于是我们用dis模块来打印func函数的字节码:

确实python用了LOAD_FAST,说明print的是一个局部变量a。这看起来可能很难以理解,但其实大家一定碰到过下面这种情况:

这段代码会在第三行报错,同样是UnboundLocalError。因为Python规定,在函数内仅被引用的变量默认为全局变量;如果在函数内被赋值,则默认为局部变量,除非有global关键词。而且显然,这个规则是在编译(生成字节码)时实现的,而不是在运行时确定的。

这个规则有人称为LEGB规则 (Local, Enclosed, Global, Builtin),就是说依次从局部,闭包,全局,内置的命名空间里寻找名字。Python的编译器在编译时按照这一规则决定究竟从哪里找变量。

在我们一开始的例子里,因为unichr在htmlescape这个局部作用域内出现了,所以Python认为可以从htmlescape的局部变量表里取到它。但是在Python2上它未被赋值,所以出现了NameError。

有了这个设定,有时我们可以用它来unset一个全局变量。(虽然好像没什么卵用。)