0.全局变量与global关键字基本概念
在编写脚本或工具时,会遇到对于全局变量的引用与赋值操作,一般而言,同一模块内,访问全局变量的方式如下:
# demo.py
var_a = 'global'
def func():
print(f'Inside: {var_a}')
func()
print(f'Outside: {var_a}')
此时执行打印为:
Inside: global
Outside: global
正如预期的那样,函数func内部正常访问获取了变量var_a的值并进行了打印。接下来需要在函数内部全局修改(更新)变量var_a的值,并编写如下代码:
# demo.py
var_a = 'global'
def func():
var_a = 'local'
print(f'Inside: {var_a}')
func()
print(f'Outside: {var_a}')
如上,在func中对var_a进行了赋值,希望var_a的值进行全局修改。运行上述代码后得到结果如下:
Inside: local
Outside: global
这与预期大相径庭,但是明明在函数内部已经能够正常访问外部变量,为什么修改时就会出问题呢?
先将这个问题搁置,引入global关键字并进一步修改代码,如下:
# demo.py
var_a = 'global'
def func():
global var_a
var_a = 'local'
print(f'Inside: {var_a}')
func()
print(f'Outside: {var_a}')
试着运行一下,结果如下:
Inside: local
Outside: local
这与我们的需求一致,但为什么使用global关键字声明后函数内部就能正常修改外部变量呢?
原因在于:
- 当访问外部变量时,由于这个动作不会修改变量的状态(值),且函数func内部没有对该变量进行显式声明,所以Python解释器会在全局变量内进行查找,当存在同名的全局变量时,解释器缺省认为此时需要访问的是这个全局变量。
- 当修改外部变量时,由于赋值这个动作对于变量是一种破坏性行为(已有值被覆写),在没有使用global关键字声明引用外部变量的情况下,Python解释器为安全起见会将其理解为局部变量赋值,并创建函数内局部变量。当函数执行完毕时,局部变量销毁,不对同名全局变量产生影响。
在使用global关键字后,显式的声明了func内部使用的变量为全局变量,可以通过查看变量内存地址验证:
# demo.py
var_a = 'global'
print(id(var_a))
def func():
global var_a
print(id(var_a))
var_a = 'local'
print(f'Inside: {var_a}')
func()
打印可得:
2240955470064
2240955470064
Inside: local
此时,函数内外var_a变脸内存指向一致,成功在函数内作用域访问修改外部全局变量。
1.问题引出
在编写项目级工程时遇到对于全局变量的操作,可以如上文所讲使用global关键字实现。但是这时需求有了一些变化:在不同模块之间实现全局变量的修改,并在引用该变量的其他模块中可以同步变更。
先使用global关键字试试:
# a.py
# 定义全局变量
var_a = 'global'
def show():
print(var_a)
# b.py
from a import var_a, show # 未使用的import语句 ‘var_a’
def update():
global var_a # 全局变量 ‘var_a’在模块级别未定义
var_a = 'local'
update()
show()
执行时返回:
global
结果预期不符,并且pycahrm会很好心地给我们提示:
- 未使用的import语句 ‘var_a’
- 全局变量 ‘var_a’在模块级别未定义
这里主要看第二条警告,说明global关键字仅能在同一模块内声明使用,无法持支跨模块的情况。
那么,有什么方法可以实现这个需求吗?
2.中间文件存储
顾名思义,该方法通过使用一个文件单独将需要的全局变量进行存储,如下图所示:
- 通过global_var.py文件预定义全局变量var_a,并赋初值。
- 在文件b.py中引入global_var.py模块,编写方法show()打印var_a
- 在文件a.py中引入global_var.py模块,编写方法update()修改var_a,并引入show()方法进行打印
代码实现如下:
# global_var.py
# 定义全局变量
var_a = 'global'
# b.py
import global_var
def show():
print(global_var.var_a)
# a.py
import global_var
from b import show
def update():
global_var.var_a = 'local'
update()
show()
运行a.py,执行结果如下:
local
优点:
- 结构简洁且实现容易
- 在目录层级浅且引用关系不复杂的情况下拥有良好的可维护性
缺点:
- 仅能对全局变量模块进行全量引入,无法使用
from global_var import var_a
的方式单独引用某一变量
3.全局变量管理器
该方案与上一种相比稍为复杂,适用于模块在pip包以及其他更为复杂的情况。
该方案利用sys.modules['__main__'].__dict__
与主文件调用globals()
方法指向一致的原理,直接修改当前作用域的全局变量字典,实现全局变量管理器如下:
# global_manager.py
import sys
from typing import Any
class GlobalManager:
@staticmethod
def set_global_dct(key: str, value: Any):
glb_dct = sys.modules['__main__'].__dict__
glb_dct[key] = value
@staticmethod
def get_global_dct(key: str):
return sys.modules['__main__'].__dict__[key]
@staticmethod
def get_global_list():
return sys.modules['__main__'].__dict__.keys()
这时,修改代码如下:
# b.py
from global_manager import GlobalManager
GlobalManager.set_global_dct('var_a', 'global')
def show():
print(GlobalManager.get_global_dct('var_a'))
# a.py
from global_manager import GlobalManager
from b import show
def update():
GlobalManager.set_global_dct('var_a', 'local')
update()
show()
运行a.py,执行结果如下:
local
该方案图示如下:
优点:
- 可实现按需调用全局变量
- 不依赖单独文件定义全局变量,可在任意文件中定义。
- 可拓展性较强
缺点:
- 变量调用语法较为复杂,实际使用时需进行二次封装