对于从其他语言转到Python的人来说,下面这段代码显得很诡异:
1 2 3 |
for i in range(3): print(i) print(i) |
你期望的是i
变量不存在报错,而实际上打印结果是:
1 2 3 4 |
0 1 2 2 |
这是因为,在Pyhton中,是没有block这个概念的。
Python中的作用域只有四种,即LEGB规则:
L, local – 在lambda函数内或者def函数内部的变量
E, Enclosing-function – 闭包的作用域(了解Python的闭包可以看《闭包初探》)
G,Global – 全局作用域
B, Build-in – 内建作用域
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
In [3]: def func(): ...: a = 1 # Local ...: def inner(): ...: print(a) # 和外部函数的a构成了闭包 ...: foo = 'hello' # 这里foo是local局部变量 ...: for foo in ["hello", "python"]: # for循环并没有作用域,foo也是局部变量,会覆盖上面的foo ...: print(foo) ...: print(foo) # 局部变量foo依然存在,打印"python" ...: global s # s变成全局变量 ...: s = 12 ...: inner() ...: In [4]: func() 1 hello python python In [5]: s Out[5]: 12 |
由此看来,for循环的作用域会污染局部作用域,Python2的列表生成式也会有这个副作用,但是已经在Python3中得到了修复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
Python 3.6.3 (default, Nov 3 2017, 14:41:25) Type 'copyright', 'credits' or 'license' for more information IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help. In [1]: i = 'foo' In [2]: [i for i in range(10)] Out[2]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] In [3]: i Out[3]: 'foo' In [4]: for i in range(10): ...: print(i) ...: 0 1 2 3 4 5 6 7 8 9 In [5]: i Out[5]: 9 Python 2.7.14 (default, Sep 25 2017, 09:54:19) [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.37)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> i = 'foo' >>> [i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> i 9 >>> i = 'foo' >>> for i in range(10): ... print(i) ... 0 1 2 3 4 5 6 7 8 9 >>> i 9 |
曾经Python邮件列表中有人想“如果在for-loop中有函数引用变量,就将此变量变成for-loop局部变量”,但是造成这个的问题并不是for循环的问题,而是闭包的迟绑定。
js,php都是这样的。虽然没有块作用域,但是写代码的时候还是习惯当成有块作用域来写,不然维护代码的时候会看晕的。
嗯,知道了之后还是要留神不要不小心覆盖作用域内的变量。一刀切的解决办法就是在函数内永远不重复使用一个变量的名字(覆盖)。
Pingback: 如何学Python? – 主机说 - 权威公正的国外服务器主机测评平台
Pingback: 【转】如何学Python | KIP