wordpress 做公司网站,国内网站都要备案吗,手机网站模板怎么用,wordpress投票插件今天写代码碰到一个百思不得解为什么会出错的代码#xff0c;简化如下#xff1a;
1
2
3
4
5
6
7
x10
deffunc():
ifsomething_true():
x20
print(x)
func()
意图很明显#xff0c;首先我定义了一个全局的x#xff0c;在函数中#xff0c;如果有特殊需要#xff0c;就重新…今天写代码碰到一个百思不得解为什么会出错的代码简化如下
1
2
3
4
5
6
7
x10
deffunc():
ifsomething_true():
x20
print(x)
func()
意图很明显首先我定义了一个全局的x在函数中如果有特殊需要就重新重新赋值一下x否则就使用全局的x。
可以这段代码在运行的时候抛出这个Error
UnboundLocalError: local variable a’ referenced before assignment
研究了一番觉得挺有意思的。而且这是一个比较常见的问题在Stack Overflow的Python tag下面基本上是个周经问题。
出现赋值就是局部变量
基本的原理很简单在Python FAQ中提到了
在Python中如果变量仅仅是被引用而没有被赋值过那么默认被视作全局变量。如果一个变量在函数中被赋值过那么就被视作局部变量。
在Effective Python也提到过
Python是这样处理赋值操作的如果变量在当前的作用域已经定义过那么就会变成赋值操作。否则的话会在当前的作用域定义一个新的变量。Assigning a value to a variable works differently. If the variable is already defined in the current scope, then it will just take on the new value. If the variable doesn’t exist in the current scope, then Python treats the assignment as a variable definition. The scope of the newly defined variable is the function that contains the assignment.
重点强调一下这里的被赋值过指的是在函数体内任何地方被赋值过。无论是否会被执行到比如在if语句中甚至是变量引用之后再赋值参考下面的代码都被作为“被赋值过”都变成了局部变量。
1
2
3
4
5
6
7
In[26]:deftest_assignment():
...:printx
...:x20
...:
In[27]:test_assignment()
UnboundLocalError:localvariablexreferencedbeforeassignment
其实到这里这个问题的答案已经出来了只要是在函数体内被赋值过那么变量就是local的任何赋值之前的操作都会出现一个RuntimeError。下面会深入解释一下。
赋值操作的编译过程原理
Python文档中有关赋值语句提到
Assignment of an object to a single target is recursively defined as follows. If the target is an identifier (name):
If the name does not occur in a global statement in the current code block: the name is bound to the object in the current local namespace.
Otherwise: the name is bound to the object in the current global namespace.
就是说如果赋值操作的变量没有用global声明那么就将这个name绑定到局部名字空间否则就绑定到全局名字空间。
我们可以使用symtable这个lib验证一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
importsymtable
code
x 10
def foo():
x 1
print(x)tablesymtable.symtable(code,,exec)
foo_namespacetable.lookup(foo).get_namespace()
sym_xfoo_namespace.lookup(x)
print(sym_x.get_name())# x
print(sym_x.is_local())# True
可以看到x变量确实被绑定到了局部。使用dis库可以看到编译的代码
1
2
3
4
5
6
7
8
9
10
11
350LOAD_FAST0(x)
3LOAD_CONST1(1)
6INPLACE_ADD
7STORE_FAST0(x)
3610LOAD_GLOBAL0(print)
13LOAD_FAST0(x)
16CALL_FUNCTION1
19POP_TOP
20LOAD_CONST0(None)
23RETURN_VALUE
其中LOAD_FAST是从local的stack中读取变量名LOAD_FAST对之后字节码的优化很重要。由此可以看到的确是在局部变量找x没有找到前面并没有STORE_FAST操作引发了UnboundLocalError。
所以我的理解是Python编译建立抽象语法树的时候根据语法书建立符号表从语法书的函数体内决定符号是local的还是global是否出现assignment语句然后在编译其他语句生成字节码。
那么既然这样为什么要等到运行的时候才报错而不是编译的时候就报错呢
参考下面的代码
1
2
3
4
5
6
x10
deffoo():
ifsomething_true():
x1
x1
print(x)
如果something_true()x的赋值就会执行那么代码不会抛异常。但是编译器并不会知道这个赋值语句会不会执行。换句话说函数体内出现了赋值语句但是Python编译过程无法得知赋值语句会不会执行到的。所以只要出现了赋值语句就将变量视为局部。至于会不会出现未赋值就使用UnboundLocalError就运行看看了。
Python为什么要这样处理
这并不是缺陷而是一个设计选择。
Python不要求声明变量但是假设函数体内定义的变量都是局部变量。这比JavaScript好多了JavaScript也不要求声明变量但是如果不是var声明的变量可能在不知情的情况下修改了全局变量。《Fluent Python》7.4
PSES6的 let 也有了类似的机制叫做“temporal dead zone”参考
这应该很好理解试想一下如果在函数中引用了一个函数内不存在的变量后面又进行了赋值。而Python将这个变量当做全局变量那么可能隐式地给你覆盖了全局变量。这如果是debug起来肯定是个噩梦。
这种设计选择正是提现了Python的设计哲学“Explicit is better than implicit.”
解决方法
前面已经提到了显示地指定使用global就可以这样即使出现赋值也不会产生作为local的变量而是去改变global的变量。
但是依然存在一个问题
1
2
3
4
5
6
7
8
9
defexternal():
x10
definternal():
globalx
x1
print(x)
internal()
external()
external的x既不是local也不是global。这种情况应该使用Python3的nonlocal。这样Python不会在当前的作用域找x会去上一层找。
可惜Python2不支持nonlocal但是我们可以使用“闭包”来解决。其实思想就是如果我们无法改变不可变的对象就将这个对象变成可以改变的对象。
1
2
3
4
5
6
7
8
defexternal():
x[10]
definternal():
x[0]1
print(x)
internal()
external()# [11]
如上代码x不是一个不可改变的int而是一个可变的list对象。这样x[0] 1就会变成一个赋值操作而不会申请新的变量。
2018年8月30日更新最近读《代码之髓》这本书对 Python 的作用域以及它的行为有了新的理解。Python 是静态作用域的而且变量无须声明赋值即声明。像 PerlJavaScript 这样的需要是需要声明的比如带上 var 就是局部变量否则就是全局变量。Python 这种赋值即声明的方式好处就是我们在写的时候很爽一般都符合我们的直觉。缺点就是在嵌套函数内部如果想要赋值那么依据“赋值即声明”我们就会创建新的变量而不会去修改外部函数的变量。
与之类似的语言是 Ruby在 Ruby 中同样“赋值即声明”不过行为却与 Python 恰恰相反。
在 Ruby 中如果嵌套方法外部方法的变量在内部方法中依然视作外部方法的变量如果在内部方法创建变量那么只会存在于内部方法中不会影响外部方法。通俗一点如果内部方法对一个变量 a 赋值的话如果外部方法有 a 那么外部方法的 a 的值会被修改否则会在内部方法创建一个 a内部方法结束之后a 就不存在了。一下代码为例
1
2
3
4
5
6
7
8
deffoo()
xold
lambda{xnew;ynew}.call# 相当于一个内部方法
px# 外部的 x 被修改成 new
py# y 是内部方法创建的不存在于外部方法中报错
end
foo
参考资料
Effective PythonItem 15: Know How Closures Interact with Variable Scope