Python的变量作用域

Python的变量作用域中分为四个级别,简称为:BGEL,作用域的级别依次升高,级别最高的是Local,如果该变量在Local中已经声明并赋值,将优先使用Local中的变量对应的值

Python变量作用域的四种情况:

  • B:build-in 系统固定模块里面的变量,也叫系统变量,比如int
  • G:global 全局变量,也就是模块级别定义的变量
  • E:enclosing 嵌套的父级函数的局部作用域,就是包含此函数的上层函数的局部作用域
  • L:local 局部作用域,即为函数中定义的变量

E和L是相对的,E中的变量相对上层来说也是L

Python的变量作用域可以用下图表示:

一个变量使用哪个域中定义的值,取决于它的相对位置

从上图中看,如果从Local的位置向上看,最先看到的是Local中的变量,其次是Enclosing中的变量,再次是Global中的变量,最后才是Build-in变量

变量的取值取决于你在哪个位置,比如你在E和L中间,那同名的这个变量肯定是会向前看,取E中的值

Python作用域的产生

在Python中,没有块级作用域。只有模块(module),类(class)以及函数(def,lambda)才会引入新的作用域

1
2
3
4
if True:
name = "PolarSnow"

print(name)

以上代码是可以正常运行的,但是在Java(有块级作用域)中运行就会报,变量未定义的错误

1
2
3
4
5
def func():
name = "Polarsnow"

func()
print(name)

以上代码执行就会报错,name变量未定义

Python变量作用域

L(Local)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int = 7

def func2():
int = 6
def func():
int = 5
print(int)
return func

f = func2()
f()

------------
5

LEG都对int变量自定义了值,但是最后取得是L中的值

E(Enclosing)

1
2
3
4
5
6
7
8
9
10
11
12
13
int = 7

def func2():
int = 6
def func():
print(int)
return func

f = func2()
f()

------------
6

在local中取值,但是local中没有,就会去E里面找

G(Global)

1
2
3
4
5
6
7
8
9
10
11
12
int = 7

def func2():
def func():
print(int)
return func

f = func2()
f()

------------
7

L和E中都没有找到int的值,继续向上找,在G中找到了int的值

B(Build-in)

1
2
3
4
5
6
7
8
9
10
def func2():
def func():
print(int)
return func

f = func2()
f()

------------
<class 'int'>

int变量的值在LEG中都没有找到,但是int是内建变量,在系统中已经对其有定义,最后在B中,找到了int的值

如果一个变量在local中查找,查找到B中还没有找到,就会报变量未定义的错误

global & nonlocal 关键字

global

函数内部可以访问全局变量中的值,但是不能修改全局变量的值。如果需要修改,要加上global关键字

1
2
3
4
5
6
7
8
9
name = "ps"

def readonly():
print("inner --->", name)

readonly()

------------
ps

函数内部是可以访问全局变量的值的,原则 –> GBEL

1
2
3
4
5
6
7
8
9
10
11
name = "ps"

def readonly():
global name
name = "PolarSnow"

readonly()
print(name)

------------
PolarSnow

加上了global关键字,就可以修改全局变量的值

nonlocal

global关键字声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量就需要nonlocal关键字了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def outer():
count = 10
def inner():
nonlocal count
count = 20
print("inner ---> ", count)
inner()
print("outer --->", count)

outer()

------------
20
20

变量作用域中的疑难杂症

1
2
3
4
5
6
7
8
9
10
name = "123"

def func1():
print(name)

def func2():
name = "456"
func1()

func2()

最后会输出什么结果?

结果是: 123

1
2
3
4
5
6
7
8
9
10
11
name = "123"

def func1():
print(name)

def func2():
name = "456"
return func1

ret = func2()
ret()

结果是: 123

上面两段代码的结果都是全局变量中的值123,这里需要特别注意的是,变量作用域在函数执行之前就已经确定了!

Python作为解释型的语言,代码从上至下执行,遇到def时,就将其函数体保存在内存的堆区,把对应的函数名保存在栈区并指向堆区的函数体,在函数执行之前,这个函数中的变量作用域就已经被记录到内存中了,不会因为后期的调用或嵌套而更改


1
2
3
4
5
6
7
8
9
10
11
l = [x + 1 for x in range(10) if x > 5]
print("display list --->", l)
print("first element --->", l[0])

print("---" * 5)

ll = [lambda :x for x in range(10) if x > 5]
print("display list --->", ll)

ret = ll[0]()
print("first element --->", ret)

结果是:

1
2
3
4
5
display list ---> [7, 8, 9, 10]
first element ---> 7
---------------
display list ---> [<function <listcomp>.<lambda> at 0x101b7b730>, <function <listcomp>.<lambda> at 0x101b7b7b8>, <function <listcomp>.<lambda> at 0x101b7b840>, <function <listcomp>.<lambda> at 0x101b7b8c8>]
first element ---> 9

问题来了,问什么使用了lambda之后,第一个元素变成9了呢?

我们把这个问题拆解一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
lll = []
for x in range(10):
if x > 5:
lll.append(x + 1)
print(lll)
print(lll[0])

llll = []
for x in range(10):
if x > 5:
def func():
return x
llll.append(func)
print(llll)
print(llll[0]())

------------
[7, 8, 9, 10]
7
[<function func at 0x10137b620>, <function func at 0x10137b6a8>, <function func at 0x10137b730>, <function func at 0x10137b7b8>]
9

为什么 x+1 时第一个元素 = 7, 而 lambda :x 时第一个元素 = 9了呢

原因就是Python程序在解释道lambda的时候,并没有执行里面的代码,而是直接放到的了内存中,随着外侧循环的结束,x的值已经变成了9,此时再把内存里保存的lambda函数拿出来执行,x变量获取到的就是当前已经变成9的这个值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
lllll = []
for x in range(10):
if x > 5:
def func(i = x):
return i
lllll.append(func)
print(lllll)
print(lllll[0]())
print(lllll[1]())
print(lllll[2]())
print(lllll[3]())

------------
[<function func at 0x101b7b620>, <function func at 0x101b7b6a8>, <function func at 0x101b7b730>, <function func at 0x101b7b7b8>]
6
7
8
9

上面的代码仅仅小修改了一下,在func中执行一条赋值语句,结果就立刻不一样了,这次列表的第一个值变成了6

之前不是说代码运行到def就直接保存到内存,不执行了吗?没错,函数体是没有被执行,但是函数的参数是个赋值表达式,被Python解释器执行了,获取到了每个循环的x的值,并赋值给函数自己内部的变量(local)

当程序执行完毕,运行列表中第一个函数取值的时候,该函数取到的是函数内部的变量i,所以最后的结果和上面是截然不同的