Python模块全局变量

Python模块全局变量

遇到的问题

我有一个主程序文件和两个模块文件,模块B定义了一个全局变量,最开始为None,主程序根据命令行参数将它重新绑定到一个类对象,模块A再引用这个变量时,发现还是None。

1
2
3
4
5
6
7
8
9
# B.py
global_yang = None

class Yang():
pass

def initialize_yang():
global global_yang
global_yang = Yang()
1
2
3
4
5
# A.py
from B import global_yang

def get_variable():
return global_yang
1
2
3
4
5
6
7
8
9
# main.py

from B import global_yang, initialize_yang
from A import get_variable

initialize_yang()

print("In main:", global_yang)
print("In A:", get_variable())

运行python main.py,所有的结果都是None。

两种import的区别

在 Python 中,模块(module)就是一个 Python 文件(*.py)。模块有自己的独立命名空间,模块中的变量、函数、类等都属于这个命名空间。我们有两种方式使用模块中的函数或变量,import modulefrom module import ..,这两个操作的行为有一些差异。

当使用 import module 时:

  1. 首次导入
    • Python 会将模块代码加载到内存中,并创建一个新的命名空间来存储模块中的变量、函数和类。
    • 模块会被注册到 sys.modules,后续导入直接从缓存中读取。
  2. 模块是独立的
    • 每个模块都有自己的命名空间,变量、函数不会泄漏到其他模块中。
    • 如果要访问另一个模块中的变量,必须显式使用 module.variable
1
2
3
4
5
# moduleA.py
x = 10

def get_x():
return x
1
2
3
4
5
6
7
8
# main.py
import moduleA

print(moduleA.x) # 访问 moduleA 的变量,输出 10
print(moduleA.get_x()) # 调用 moduleA 的函数,输出 10

# 错误示例:
print(x) # 抛出 NameError,因为 x 在 moduleA 的命名空间中

from module import variable 的作用是将模块中的某个变量(或者函数、类)直接导入到当前命名空间。在 Python 中,一切皆对象。变量并不是数据本身,而只是对存储的数据对象的引用。使用 from module import variable 导入的变量是模块变量的引用拷贝,这相当于静态拷贝了原有模块变量的地址,当模块变量重新绑定到一个新的地址后,这个导入的变量不会动态发生改变。

1
2
3
# moduleC.py
x = 10
y = 20
1
2
3
4
5
6
7
# main.py
from moduleC import x
import moduleC

moduleC.x = 30 # 修改模块变量
print(moduleC.x) # 输出 30,moduleC 的 x 被更新
print(x) # 输出 10,本地 x 不会更新

原有程序的错误

1
2
3
4
5
6
7
8
9
# B.py
global_yang = None

class Yang():
pass

def initialize_yang():
global global_yang
global_yang = Yang()
  • global_yang 初始值为 None
  • 调用 initialize_yang() 时,将 global_yang 重新绑定到一个新的 Yang 实例。
1
2
3
4
5
# A.py
from B import global_yang

def get_variable():
return global_yang
  • from B import global_yang 导入了 global_yang 的值(当前为 None)。
  • 这里的 global_yangB.global_yang 的引用拷贝。
  • 后续如果 B.global_yang 被重新绑定,A.py 中的 global_yang 不会同步变化。
1
2
3
4
5
6
7
8
# main.py
from B import global_yang, initialize_yang
from A import get_variable

initialize_yang()

print("In main:", global_yang) # 输出 None
print("In A:", get_variable()) # 输出 None
  • initialize_yang()B.global_yang 重新绑定为 Yang 实例。
  • main.py 中的 global_yangA.py 中的 global_yang 都是拷贝的旧引用,仍然指向初始的 None

核心机制其实就是,当使用 from B import global_yang 时,Python 实际进行的操作是拷贝当前的引用值到本地命名空间

  • 导入时:
    • global_yang 的值为 None,因此本地的 global_yang 引用指向了 None
  • 重新绑定时:
    • B.global_yang = Yang() 修改了模块命名空间的变量绑定。
    • 但本地的 global_yang 仍然指向旧的对象 None

Python 中的这种行为是有意设计的:

  • 提高性能:导入变量的拷贝比每次动态查找更快。
  • 明确作用域:避免模块变量的动态绑定导致隐式修改。

怎么办

简单的方式就是不直接导入变量,而是导入整个模块,通过模块命名空间访问变量。

1
2
3
4
5
# A.py
import B

def get_variable():
return B.global_yang # 动态访问 B 的变量
1
2
3
4
5
6
7
8
# main.py
import B
from A import get_variable

B.initialize_yang()

print("In main:", B.global_yang) # 输出 Yang 实例
print("In A:", get_variable()) # 输出 Yang 实例

总结

1. 模块是独立命名空间

  • 模块中的变量、函数、类都存在于模块自己的命名空间中。
  • 使用 import module 访问时,模块变量的更新是全局可见的。

2. from ... import ... 是引用拷贝

  • 使用 from module import variable 导入变量时,变量的引用被拷贝到本地命名空间。
  • 如果模块中重新绑定变量,本地拷贝不会反映这些更新。

3. 一句话

Python 的模块机制强调隔离性和独立性,但通过 from ... import ... 可能导致静态引用问题,因此推荐优先使用 import module,确保代码清晰、动态且可维护。