首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >那天,我的Python函数死活改不了全局变量

那天,我的Python函数死活改不了全局变量

原创
作者头像
风一样的男子
发布2026-06-10 11:07:59
发布2026-06-10 11:07:59
430
举报
文章被收录于专栏:编程教程编程教程

一个让人抓狂的下午

小李刚学Python不久,接了个小任务:写个程序统计用户输入的数字,并且实时显示当前的总和。

他很快写出了这样的代码:

代码语言:javascript
复制
total = 0  # 全局总和

def add_number(n):
    total = total + n
    print(f"加了{n},现在总和是{total}")

add_number(5)

运行一下,报错了:

代码语言:javascript
复制
UnboundLocalError: local variable 'total' referenced before assignment

小李懵了。total明明在上面定义好了,为什么说它没定义?

他试了另一个办法——在函数里把total打印出来看看:

代码语言:javascript
复制
total = 100

def test():
    print(total)

test()  # 输出100

咦,这又能读到。那为什么刚才不能修改?

这就是Python新手最容易踩的坑之一:在函数里读全局变量OK,写就不行

今天我们就用三个场景,把这个问题彻底说清楚。


场景一:只读不写——直接使用全局变量

先弄清楚刚才那个能正常工作的例子。

代码语言:javascript
复制
count = 10

def show():
    print(count)

show()  # 10

为什么这个不报错?

因为Python在函数里找变量时,遵循一个简单的顺序:先看函数内部有没有定义,没有就往外层找

show()函数里,print(count)这句话,Python会问:

  1. 函数内部有没有一个叫count的局部变量?没有。
  2. 那上一层(全局作用域)有没有?有,值是10。

OK,拿来用。

这就像你在家里找充电器:先看看自己包里有没有,没有就去客厅找。找到了就用,没问题。

规则一:当你只是读取一个变量的值,而不给它赋值时,Python会自动向上查找,找到全局变量就用。

适用范围很广:打印日志、读取配置常量、使用全局计数器(只读)等。

但是注意——这个“不赋值”是关键。哪怕你在同一行代码里既读又写,情况就变了。


场景二:要修改——用global声明

回到小李报错的那个例子:

代码语言:javascript
复制
total = 0

def add_number(n):
    total = total + n

这里有一个total = ...,这对Python来说意味着:**我要在函数内部创建一个局部变量叫total**。

但等号右边又用到了total,这时局部变量total还没创建好呢(赋值还没完成),结果就是“在定义前就使用了”。

Python的规则是:如果在函数内对变量进行赋值,该变量默认被视为局部变量

解决方案:明确告诉Python,“这个变量不是局部的,我要用全局那个”。

代码语言:javascript
复制
total = 0

def add_number(n):
    global total
    total = total + n

add_number(5)
print(total)  # 5
add_number(3)
print(total)  # 8

加了global total之后,函数里的total就不是局部变量了,而是指向全局那个。

你可以把global理解为声明:“接下来的total,就是外面那个,别给我新建局部的。”

规则二:函数内部要修改全局变量,必须先用global语句声明。

几点要注意:

  • global声明要放在使用变量之前,通常放在函数开头。
  • 可以同时声明多个变量:global a, b, c
  • 如果只读不写,不需要global;一旦要改,就必须加。

有人会问:那如果全局变量是列表、字典,我修改它的内容(比如append),需要global吗?

这是个好问题。看代码:

代码语言:javascript
复制
my_list = [1, 2, 3]

def add_item():
    my_list.append(4)  # 没有global

add_item()
print(my_list)  # [1, 2, 3, 4]

竟然没报错?

原因在于:append是在修改对象的内容,而不是给变量重新赋值。变量my_list始终指向同一个列表对象,我们没有做my_list = ...这种重新绑定的操作。

同理:

代码语言:javascript
复制
my_dict = {"a": 1}

def update():
    my_dict["b"] = 2  # OK,不需要global

def reassign():
    my_dict = {"c": 3}  # 需要global,因为这是重新赋值

记住这个区分:改内容 vs 改指向。改内容不用global,改指向(赋值)就要用。


场景三:跨文件共享——用模块级变量

上面两个场景都在同一个文件里。但真实项目通常会拆成多个模块(多个.py文件)。

假如你有两个文件:

config.py

代码语言:javascript
复制
# 全局配置
app_name = "我的应用"
version = "1.0"
user_count = 0

main.py

代码语言:javascript
复制
import config

def increment_user():
    config.user_count += 1

def show_info():
    print(f"{config.app_name} v{config.version},用户数:{config.user_count}")

increment_user()
show_info()

这里用了config.user_count而不是直接user_count

为什么这样就能改?原因不是global,而是通过模块名去访问变量

当你写config.user_count = ...时,Python知道你是在修改config模块里的属性,不会把它当成局部变量。模块对象像个“容器”,你明确告诉了Python去改容器里的东西。

这种方式的好处很明显:

  • 不需要在每个函数里写global声明
  • 多个模块可以共享同一份配置
  • 代码更清晰,一看就知道变量来自哪里

实际项目中,你可以单独建一个globals.py或者state.py,把所有需要共享的变量集中管理:

代码语言:javascript
复制
# state.py
is_logged_in = False
current_user = None
score = 0

其他模块统一用import state来读写。

规则三:跨文件共享全局状态,用模块级变量,通过模块名访问。


深入一点:为什么Python要这样设计?

你可能觉得:“别的语言全局变量随便改,Python怎么这么麻烦?”

这是有意为之的。Python的设计哲学有一条:显式优于隐式

如果一个函数能随便修改全局变量,你看到一行count = count + 1,很难判断这个count是局部还是全局。大型项目里,这种不确定性会导致极其难追踪的bug。

Python逼你写global,相当于让你明确表态:“我知道我在改全局变量,我负责。”

同样,通过模块名访问,也是在增加可读性。读代码的人看到state.score,马上知道这个score来自外部的共享状态。


三个技巧帮你少踩坑

技巧1:尽量少用全局变量

这不是说教,是真心建议。全局变量多了,程序就像一堆乱线缠在一起。函数调用的顺序会影响全局变量的值,调试起来非常痛苦。

更好的做法:把需要共享的状态封装成对象,或者通过参数传递、返回值接收。

对比一下:

代码语言:javascript
复制
# 不推荐:到处改全局
total = 0

def add(x):
    global total
    total += x

def subtract(x):
    global total
    total -= x
代码语言:javascript
复制
# 推荐:用类封装
class Calculator:
    def __init__(self):
        self.total = 0
    
    def add(self, x):
        self.total += x
    
    def subtract(self, x):
        self.total -= x

或者更简单的函数式风格:

代码语言:javascript
复制
def add(total, x):
    return total + x

total = 0
total = add(total, 5)

技巧2:用global之前,先问问自己能不能用返回值替代

很多情况下,你并不需要改全局变量。让函数返回新值,调用方去更新全局变量,逻辑更清晰。

代码语言:javascript
复制
# 不推荐
counter = 0
def increment():
    global counter
    counter += 1

# 推荐
counter = 0
def increment(c):
    return c + 1
counter = increment(counter)

技巧3:在函数开头集中声明所有用到的全局变量

如果确实需要用global,把所有声明放在函数顶部,一目了然:

代码语言:javascript
复制
def complex_operation():
    global total, count, status
    # 清晰的声明
    # 下面再用这些变量

不要写到一半突然冒出一个global,容易乱。


总结对比表

场景

做法

是否需要global

举例

函数内只读取全局变量

直接用

print(total)

函数内修改全局变量(重新赋值)

先用global声明

global total; total = 10

修改全局可变对象的内容

直接用

my_list.append(1)

跨文件访问共享变量

import模块后,通过模块名访问

config.user_count = 10


一个小练习帮你巩固

下面这段代码有几处错误?试着找出来并改正。

代码语言:javascript
复制
score = 100
history = []

def update_score(new_score):
    score = new_score  # 意图是修改全局score

def add_record(record):
    history.append(record)

def reset():
    score = 0
    history = []

update_score(90)
add_record("update")
reset()
print(score)  # 期望输出0,实际是多少?

答案:

  1. update_score里的score = new_score创建了局部变量,没改到全局的。需要加global score
  2. reset里,score = 0也是局部,同样需要global score
  3. resethistory = []创建了新的局部变量,外面的history没变。这里如果想要清空列表,应该用history.clear()而不是重新赋值。如果要重新赋值,需要global history

修正后的版本:

代码语言:javascript
复制
score = 100
history = []

def update_score(new_score):
    global score
    score = new_score

def add_record(record):
    history.append(record)  # 改内容,不用global

def reset():
    global score
    score = 0
    history.clear()  # 清空内容,不用global

update_score(90)   # score变成90
add_record("update")
reset()            # score变成0,history清空
print(score)       # 0

最后说几句

全局变量不是洪水猛兽,小脚本、快速原型、配置类变量用起来很方便。但随着代码增长,函数和全局变量的耦合会越来越紧。

记住一句话:函数最好只依赖它的参数,只通过返回值影响外部。这样的函数容易测试、容易复用、容易理解。

当你下次在函数里想写global的时候,停一下,想一想:有没有办法用参数和返回值来实现?

如果没有,那就放心用。Python给了你这个工具,合理使用就行。

小李后来学明白了这个知识点,再也没被UnboundLocalError困住过。希望你也是。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一个让人抓狂的下午
  • 场景一:只读不写——直接使用全局变量
  • 场景二:要修改——用global声明
  • 场景三:跨文件共享——用模块级变量
  • 深入一点:为什么Python要这样设计?
  • 三个技巧帮你少踩坑
  • 总结对比表
  • 一个小练习帮你巩固
  • 最后说几句
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档