模块级变量仅在本模块命名空间内有效,需显式导入才能访问,非真正全局;其生命周期始于首次导入、终于解释器退出;多线程/请求下共享易引发并发问题,应避免直接用于存储请求级数据。

全局变量和模块执行上下文里的“全局”变量,名字相似但作用范围完全不同。Python 中没有真正跨进程、跨请求的“全局”,所谓“全局”总是相对于某个作用域而言的。关键在于:模块级变量 ≠ 全局变量,它只是该模块在当前 Python 解释器实例中的一份命名空间数据。
看作用域边界:模块顶层定义 ≠ 全局可访问
模块文件(如 config.py)里写的 DEBUG = True 或 counter = 0,只属于这个模块的命名空间。其他文件必须通过 import config 或 from config import DEBUG 才能访问——它不会自动出现在内置命名空间或所有模块里。
- 你在 main.py 中直接写 print(DEBUG) 会报
NameError,因为没导入 - 即使两个模块都写了 user_cache = {},它们是完全独立的两份字典,互不影响
-
__builtins__ 里的东西(如
print、len)才是真正的“全局可用”,而模块变量不是
看生命周期:导入即存在,非运行时动态生成
模块级变量在模块被首次 import 时完成初始化,之后一直驻留在该模块的命名空间中,直到解释器退出。它不随函数调用而创建或销毁。
- 推荐写法:模块顶部直接赋值,比如 MAX_RETRY = 3、DB_URL = "sqlite:///app.db"
- 避免在函数里用 global 延迟初始化,例如 def init(): global DB_URL; DB_URL = ... —— 容易漏调、顺序难控
- 模块末尾的 if __name__ == "__main__": 块不影响导入行为;变量必须在该块之外定义才能被其他模块稳定使用
看并发行为:多线程/多请求下它就是共享的“雷区”
在 Flask、FastAPI 等 Web 框架或多线程程序中,模块变量会被所有线程/请求共用。A 请求修改了 current_user_id,B 请求立刻能看到——这不是特性,是隐患。
- ❌ 错误用法:在路由函数中读写模块级 temp_result = [] 存用户数据
- ✅ 正确替代:flask.g(单请求)、contextvars.ContextVar(协程安全)、threading.local()(单线程隔离)、Redis/数据库(跨请求持久)
- 特别注意:contextvars 必须在模块顶层创建(如 req_id = ContextVar("req_id")),不能在函数内或闭包里新建
看命名与意图:用命名规范强化语义区分
变量名本身就能传递作用域信息。模块级共享状态建议用全大写加下划线,和局部变量形成视觉隔离,也暗示“这是有意暴露的配置或状态”。
- ✅ 推荐:API_TIMEOUT_SEC = 30、IS_MAINTENANCE_MODE = False
- ⚠️ 警惕:cache = {}、data = None 这类命名模糊的变量,容易误以为是临时局部变量
- 如果真需要跨模块统一状态,应明确封装进单独模块(如 state.py),并通过 import state 访问,而非到处重复定义

















