对于从其他语言转到Python的人来说,下面这段代码显得很诡异:

Python

1

2

3

for i in range(3):

    print(i)

print(i)

你期望的是i变量不存在报错,而实际上打印结果是:

Python

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 – 内建作用域

举个例子:

Python

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中得到了修复。

Python

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循环的问题,而是闭包的迟绑定