最近碰到这么个问题,有这么个函数,用来将HTML转义字符变回原来的字符:
Python
def htmlescape(s):
if sys.version_info[0] == 3: # python 3.x
unichr = chr
def replc(match):
dict={'amp':'&','nbsp':' ','quot':'"','lt':'','copy':'©','reg':'®'}
if len(match.groups()) >= 2:
if match.group(1) == '#':
return unichr(int(match.group(2)))
else:
return dict.get(match.group(2), '?')
htmlre = re.compile("&(#?)(\d{1,5}|\w{1,8}|[a-z]+);")
return htmlre.sub(replc, s)
1
2
3
4
5
6
7
8
9
10
11
12
defhtmlescape(s):
ifsys.version_info[0]==3:# python 3.x
unichr=chr
defreplc(match):
dict={'amp':'&','nbsp':' ','quot':'"','lt':'','copy':'©','reg':'®'}
iflen(match.groups())>=2:
ifmatch.group(1)=='#':
returnunichr(int(match.group(2)))
else:
returndict.get(match.group(2),'?')
htmlre=re.compile("&(#?)(\d{1,5}|\w{1,8}|[a-z]+);")
returnhtmlre.sub(replc,s)
其中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这个内置函数的,为啥就变成未定义呢?
为了搞清楚问题,我们用了一个最小化的测试用例:
Python
a = 1
def func():
if False:
a = 2
print(a)
1
2
3
4
5
a=1
deffunc():
ifFalse:
a=2
print(a)
运行到print那行报错“UnboundLocalError: local variable ‘d’ referenced before assignment”。我们注意到这时报错的是local variable没有定义。明明a之前是一个全局变量而且if根本不会执行啊。于是我们用dis模块来打印func函数的字节码:
3 0 LOAD_GLOBAL 0 (False)
3 POP_JUMP_IF_FALSE 15
4 6 LOAD_CONST 1 (2)
9 STORE_FAST 0 (a)
12 JUMP_FORWARD 0 (to 15)
5 >> 15 LOAD_FAST 0 (a)
18 PRINT_ITEM
19 PRINT_NEWLINE
20 LOAD_CONST 0 (None)
23 RETURN_VALUE
1
2
3
4
5
6
7
8
9
10
11
12
30LOAD_GLOBAL0(False)
3POP_JUMP_IF_FALSE15
46LOAD_CONST1(2)
9STORE_FAST0(a)
12JUMP_FORWARD0(to15)
5>>15LOAD_FAST0(a)
18PRINT_ITEM
19PRINT_NEWLINE
20LOAD_CONST0(None)
23RETURN_VALUE
确实python用了LOAD_FAST,说明print的是一个局部变量a。这看起来可能很难以理解,但其实大家一定碰到过下面这种情况:
Python
a = 1
def func():
a = 2
print(a)
1
2
3
4
a=1
deffunc():
a=2
print(a)
这段代码会在第三行报错,同样是UnboundLocalError。因为Python规定,在函数内仅被引用的变量默认为全局变量;如果在函数内被赋值,则默认为局部变量,除非有global关键词。而且显然,这个规则是在编译(生成字节码)时实现的,而不是在运行时确定的。
这个规则有人称为LEGB规则 (Local, Enclosed, Global, Builtin),就是说依次从局部,闭包,全局,内置的命名空间里寻找名字。Python的编译器在编译时按照这一规则决定究竟从哪里找变量。
在我们一开始的例子里,因为unichr在htmlescape这个局部作用域内出现了,所以Python认为可以从htmlescape的局部变量表里取到它。但是在Python2上它未被赋值,所以出现了NameError。
有了这个设定,有时我们可以用它来unset一个全局变量。(虽然好像没什么卵用。)