当前位置:首页>python>Python3 命名空间和作用域:变量住在哪里,谁能找到它

Python3 命名空间和作用域:变量住在哪里,谁能找到它

  • 2026-10-12 15:21:07
Python3 命名空间和作用域:变量住在哪里,谁能找到它

Python3 命名空间和作用域:变量住在哪里,谁能找到它

我是陈默,一个正拼命上岸的码农。

你有没有遇到过这种事?

明明定义了一个变量,换个地方就找不到了。或者两个地方都叫 x,但值不一样。

这不是 bug,这是 Python 的命名空间和作用域在"管事"。

搞懂它,你就能彻底理解变量的"可见性"——谁看得见谁,谁看不见谁。


1. 命名空间:变量住在哪个房间

命名空间就是一个"名字到对象"的映射。说白了,就是一个字典,记录着每个名字对应什么值。

三种命名空间

# 1. 内置命名空间:Python 自带的print(len([1, 2, 3]))     # len 是内置的print(max(1, 2, 3))       # max 也是# 2. 全局命名空间:模块级别的name = "陈默"# 全局变量score = 85# 全局变量# 3. 局部命名空间:函数内部的defgreet():    message = "你好"# 局部变量    print(message)

三种命名空间就像三层楼。

  • 一楼:内置的,谁都能用(len、print、max)
  • 二楼:全局的,这个文件里都能用(name、score)
  • 三楼:局部的,只有函数内部能用(message)

查看命名空间

# 查看全局命名空间x = 10y = 20print(globals()["x"])    # 输出: 10# 查看局部命名空间(在函数里)defshow():    a = 1    b = 2    print(locals())show()    # 输出: {'a': 1, 'b': 2}

2. 作用域:变量的"有效范围"

作用域就是——一个变量在哪些地方能被访问到。

LEGB 规则

Python 查找变量时,按这个顺序找:

L → E → G → B

  • Local:函数内部
  • Enclosing:外层函数(嵌套函数时)
  • Global:模块全局
  • Built-in:Python 内置
# B:内置# len、print、max 这些# G:全局message = "全局消息"defouter():# E:外层函数    message = "外层消息"definner():# L:局部        message = "局部消息"        print(message)    inner()outer()    # 输出: 局部消息

Python 从内到外找。找到了就用,不再往外找了。

验证 LEGB

x = "全局的 x"defouter():    x = "外层的 x"definner():        x = "局部的 x"        print(x)        # 找到局部的,不再往外找    inner()    print(x)            # 找到外层的outer()print(x)                # 找到全局的# 输出:# 局部的 x# 外层的 x# 全局的 x

把 inner() 里的 x = "局部的 x" 删掉试试:

x = "全局的 x"defouter():    x = "外层的 x"definner():        print(x)        # 没有局部的 x,找外层的    inner()outer()# 输出: 外层的 x

再把 outer() 里的也删掉:

x = "全局的 x"defouter():definner():        print(x)        # 外层也没有,找全局的    inner()outer()# 输出: 全局的 x

一层一层往外找。这就是 LEGB。


3. 局部变量 vs 全局变量

基本规则

count = 0# 全局变量defincrement():    count = 1# 这是局部变量!不是全局的那个    print(f"函数内:{count}")increment()        # 输出: 函数内:1print(f"函数外:{count}")    # 输出: 函数外:0

函数内部赋值,创建的是局部变量。不会动全局变量。

函数内可以读取全局变量

name = "陈默"defgreet():    print(f"你好,{name}")    # 读取全局变量,没问题greet()    # 输出: 你好,陈默

但不能直接修改全局变量

count = 0defincrement():    count += 1# 报错!UnboundLocalErrorincrement()

Python 看到函数里有 count =,就认为 count 是局部变量。但还没赋值就读了,所以报错。


4. global:告诉 Python 我要用全局变量

count = 0defincrement():global count       # 声明:我要用全局的 count    count += 1increment()increment()increment()print(count)    # 输出: 3

global 告诉 Python:"别创建局部变量了,用外面的那个。"

什么时候用 global?

老实说,尽量少用。

# ❌ 滥用 globaltotal = 0defadd(x):global total    total += x# ✅ 用返回值代替defadd(x, total):return total + xtotal = 0total = add(5, total)total = add(3, total)print(total)    # 输出: 8

好的函数应该通过参数接收数据、通过返回值输出结果。不偷着改外面的变量。


5. nonlocal:修改外层函数的变量

global 改全局的,nonlocal 改外层函数的。

defcounter():    count = 0defincrement():nonlocal count    # 声明:我要用外层的 count        count += 1return countreturn incrementc = counter()print(c())    # 输出: 1print(c())    # 输出: 2print(c())    # 输出: 3# 创建新的计数器,互不影响c2 = counter()print(c2())   # 输出: 1

这就是闭包。nonlocal 让内部函数能修改外层函数的变量。


6. 常见陷阱

陷阱一:循环变量泄漏

x = 10for x in range(5):passprint(x)    # 输出: 4(全局的 x 被循环改了!)

Python 的 for 循环不会创建新的作用域。循环变量会修改外层的同名变量。

陷阱二:列表推导式的作用域

x = "全局"# 列表推导式有自己的作用域result = [x for x in range(3)]print(x)    # 输出: 全局(没被改,推导式有自己的作用域)

列表推导式、生成器表达式、字典/集合推导式,都有自己的作用域。

陷阱三:默认参数的可变对象

# ❌ 默认参数是可变对象defadd_item(item, items=[]):    items.append(item)return itemsprint(add_item("苹果"))    # 输出: ['苹果']print(add_item("香蕉"))    # 输出: ['苹果', '香蕉'](不是空的!)# ✅ 用 None 代替defadd_item(item, items=None):if items isNone:        items = []    items.append(item)return itemsprint(add_item("苹果"))    # 输出: ['苹果']print(add_item("香蕉"))    # 输出: ['香蕉'](正确)

默认参数在函数定义时只创建一次。可变对象(列表、字典)会被所有调用共享。


7. globals() 和 locals()

globals():查看和修改全局命名空间

x = 10y = 20# 查看所有全局变量g = globals()print(g["x"])     # 输出: 10# 甚至可以动态创建变量(不推荐,但了解一下)g["z"] = 30print(z)          # 输出: 30

locals():查看局部命名空间

defshow_locals(a, b):    c = a + b    print(locals())show_locals(1, 2)# 输出: {'a': 1, 'b': 2, 'c': 3}

注意:在模块级别,locals() 和 globals() 是一样的。


8. 实战:理解作用域链

# 一个综合例子total = 0# 全局defprocess(data):"""处理数据,累加到 total"""global total    count = 0# process 的局部defhandle(item):nonlocal countif item > 0:            count += 1return item * 2return0    results = []for item in data:        result = handle(item)        results.append(result)        total += result    print(f"处理了{count}个有效数据")return resultsdata = [5, -1, 3, 0, 8, -2, 7]print(f"处理结果:{process(data)}")print(f"总计:{total}")# 输出:# 处理了4个有效数据# 处理结果:[10, 0, 6, 0, 16, 0, 14]# 总计:46

这个例子里有:

  • total:全局变量,用 global 修改
  • count:process 的局部变量,用 nonlocal 在 handle 中修改
  • data、results:process 的局部变量
  • item、result:循环中的临时变量

最后

命名空间和作用域,说白了就是回答一个问题:这个变量,在这个位置,能不能被找到?

记住三件事:

  1. LEGB 规则:从内到外找变量——局部 → 外层函数 → 全局 → 内置
  2. 函数内部赋值创建局部变量。想改全局变量,用 global;想改外层函数的变量,用 nonlocal
  3. 尽量少用 global。用参数和返回值传递数据,代码更安全

我的建议:

打开你的编辑器,写几个嵌套函数,每个层级都定义同名变量。然后一层一层删掉,观察打印结果的变化。亲眼看到 LEGB 的查找过程,比看十遍文章都管用。

理解作用域,就是理解变量的"边界"。边界清楚了,bug 就少了。

今天就到这里。

我是陈默,我们下期再见。


如果你觉得这篇文章有帮助,欢迎关注我。我会持续分享 Python 学习的干货。

最新文章

随机文章