0%

作用域

Python 变量作用域:变量在哪儿能用,Python 按这个顺序找

写 Python 的时候,有时候会遇到这种奇怪的情况:

明明在外面定义了一个变量,函数里一用就报错 UnboundLocalError;或者在函数里改了一个值,外面却纹丝不动。

这些问题都跟变量作用域有关——变量在哪儿能用,Python 是按照一套固定规则去查找的。

变量能找到,全靠 LEGB

Python 找变量的时候,按这个顺序找:局部 → 嵌套 → 全局 → 内置

记住四个字母:L → E → G → B

缩写 全称 啥意思 举个例子
L Local 当前函数里定义的变量 函数内部的 x = 1
E Enclosing 外层函数的变量(嵌套函数场景) 外函数里的 x,内函数能用
G Global 模块顶层的变量 .py 文件最外面定义的变量
B Built-in Python 自带的函数和变量 printlenrange

从里往外找,找到了就用,找不到就报 NameError

局部变量(Local):函数里面的,外面用不了

函数里面定义的变量,默认就是局部变量。只在函数执行期间存在,执行完就没了。

1
2
3
4
5
6
def my_func():
local_var = 10
print(local_var) # 10

my_func()
# print(local_var) # NameError: name 'local_var' is not defined

函数的参数也是局部变量:

1
2
3
4
5
def greet(name):   # name 是参数,也是局部变量
print(name)

greet("Alice")
# print(name) # 报错,外面访问不到

局部变量名可以和全局变量同名,但它们是两个不同的东西:

1
2
3
4
5
6
7
8
x = 100   # 全局

def my_func():
x = 10 # 局部,跟全局的 x 没关系
print(x) # 10

my_func()
print(x) # 100,全局的没变

函数里优先用局部变量,找不到才往外找。

全局变量(Global):外面定义的,函数里能读,但改的时候要注意

访问全局变量,直接读就行:

1
2
3
4
5
6
message = "Hello"

def say():
print(message) # 直接读,没问题

say() # Hello

想修改全局变量?得先声明:

1
2
3
4
5
6
count = 0

def increment():
count += 1 # 这样写会报错

increment() # UnboundLocalError

为什么报错?因为 count += 1 会赋值,Python 看到赋值就认为 count 是局部变量,但局部变量还没初始化,所以报错。

正确写法是用 global 声明:

1
2
3
4
5
6
7
8
count = 0

def increment():
global count # 告诉 Python:我要用的是外面的 count
count += 1

increment()
print(count) # 1

global 的规则:必须在修改之前声明,通常放在函数的第一行。

嵌套作用域(Enclosing):函数套函数,内层能用外层的

1
2
3
4
5
6
7
8
9
10
def outer():
x = 10

def inner():
print(x) # 能访问外层的 x

inner()
print(x) # 10

outer()

内层函数能读外层函数的变量,这是 LEGB 里的 E(Enclosing) 层级。

内层函数想修改外层变量?用 nonlocal

1
2
3
4
5
6
7
8
9
10
11
def outer():
x = 10

def inner():
nonlocal x # 声明:我要改的是外层函数的 x
x = 20

inner()
print(x) # 20,被改了

outer()

nonlocalglobal 的区别:

  • global:改的是模块顶层的全局变量
  • nonlocal:改的是外层函数里的变量(在嵌套函数里用)

闭包——内层函数被返回后,外层变量还被”记住”:

1
2
3
4
5
6
7
8
9
10
def make_multiplier(n):
def multiplier(x):
return x * n # n 来自外层函数
return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)

double(5) # 10,记住了 n=2
triple(5) # 15,记住了 n=3

这个特性叫闭包,外层函数的变量 n 被内层函数”捕获”了,即使外层函数执行完了,n 依然活着。

内置作用域(Built-in):Python 自带的那些东西

printlentyperange 这些,都是 Python 内置的,属于 B(Built-in) 层级。

但不小心用了同名变量,内置函数就被”遮蔽”了:

1
2
3
4
len = 100   # 变量名跟内置函数同名

def get_length(items):
return len(items) # 报错,len 现在是数字 100,不是函数了

这个问题还挺隐蔽的。你定义了一个叫 len 的变量,就把内置的 len 函数覆盖了。listdictstrtype 这些名字也容易被覆盖。

最佳实践:别用 Python 内置函数的名字当变量名。

LEGB 查找顺序,看一个完整的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# G(全局)
x = "global"

def outer():
# E(嵌套)
x = "outer"

def inner():
# L(局部)
x = "inner"
print(x) # LEGB 先找 L,找到了

inner()
print(x) # E 层级,outer 的 x

outer()
print(x) # G 层级,global 的 x

输出:

1
2
3
inner
outer
global

inner 里面的 print(x),从 L 找到就停了,所以用的是 x = "inner"

欢迎关注我的其它发布渠道