Python中import外部模块全局变量修改规则及踩坑

最近碰到一个import外部文件全局变量修改后未符合预期效果的问题,简要描述如下:

有env.py, test.py, dal.py三个文件,env.py 中定义了DEBUG=False的全局变量,dal.py中部分代码会根据DEBUG取值决定是否走调试逻辑,在test.py中通过from env import DEBUG后,设置DEBUG=True,然而在dal.py中实际使用DEBUG时却发现DEBUG取值依然是False,并没有修改成功。简化代码如下:

# env.py
DEBUG = False
# dal.py
from env import DEBUG


def test():
    if DEBUG:
        print('DEBUG logic')
    else:
        print('online logic')

# test.py
import dal
from env import DEBUG


if __name__ == '__main__':
    DEBUG = True
    dal.test()

执行结果:

$ python test.py
online logic

一时之间觉得非常奇怪,探究了一下其具体原因,发现实际要想修改import的其他模块全局变量取值并生效,还真有些讲究在里面,这里总结分享一下。

全局变量的修改对于值类型和引用类型规则并不相同,因而以下举例中同时定义了gx/gy作为值类型代表,gdctx/gdcty作为引用类型代表,同时为了跟踪是否指向同一对象,使用id函数打印出了每一变量的对象id。

定义以下下代码文件:

# env.py
gx = 'env'
gy = 'env'
gdctx = {'env': 'env'}
gdcty = {'env': 'env'}

print('gx:{}|{}\tgy:{}|{}\tgdctx:{}|{}\tgdcty:{}|{}\tin env'.format(id(gx), gx, id(gy), gy, id(gdctx), gdctx, id(gdcty), gdcty))
# mods.py
from env import gx, gy, gdctx, gdcty
import env


def print_env():
    print('gx:{}|{}\tgy:{}|{}\tgdctx:{}|{}\tgdcty:{}|{}\tin mods'.format(id(gx), gx, id(gy), gy, id(gdctx), gdctx, id(gdcty), gdcty))
    print('gx:{}|{}\tgy:{}|{}\tgdctx:{}|{}\tgdcty:{}|{}\tin mods.env'.format(id(env.gx), env.gx, id(env.gy), env.gy, id(env.gdctx), env.gdctx, id(env.gdcty), env.gdcty))

test1.py中通过import env中的全局变量直接进行修改:

# test1.py
from env import gx, gy, gdctx, gdcty
import env
from mods import print_env


gx = 'test1' # 实际生成了新的值对象,对象id发生变动
gdctx['env'] = 'test1' # 对引用类型dict修改了k/v,对象id不变

def test():
    gy = 'test1' # 未添加global声明,实际生成了新的局部变量gy,不影响全局变量
    gdcty = {} # 未添加global声明,实际生成了新的局部变量gdcty,不影响全局变量

if __name__ == '__main__':
    print('gx:{}|{}\tgy:{}|{}\tgdctx:{}|{}\tgdcty:{}|{}\tin test1'.format(id(gx), gx, id(gy), gy, id(gdctx), gdctx, id(gdcty), gdcty))
    print('gx:{}|{}\tgy:{}|{}\tgdctx:{}|{}\tgdcty:{}|{}\tin test1.env'.format(id(env.gx), env.gx, id(env.gy), env.gy, id(env.gdctx), env.gdctx, id(env.gdcty), env.gdcty))
    print_env()

执行 test1.py:

$ python test1.py
gx:4508684848|env    gy:4508684848|env    gdctx:4509665632|{'env': 'env'}      gdcty:4509713664|{'env': 'env'}    in env  # env.py中的对象id及取值
gx:4510060784|test1  gy:4508684848|env    gdctx:4509665632|{'env': 'test1'}    gdcty:4509713664|{'env': 'env'}    in test1 # test1.py中gx已经是不同的对象id与取值, gdctx内容发生变化,但依然指向同一对象,gy、gdcty不受影响
gx:4508684848|env    gy:4508684848|env    gdctx:4509665632|{'env': 'test1'}    gdcty:4509713664|{'env': 'env'}    in test1.env # 通过env.* 形式直接引用env中的变量,对象id不变,gdctx内容发生变化
gx:4508684848|env    gy:4508684848|env    gdctx:4509665632|{'env': 'test1'}    gdcty:4509713664|{'env': 'env'}    in mods # 保持指向最开始import时的env.*对象,注意gx不受test1.py中的修改影响
gx:4508684848|env    gy:4508684848|env    gdctx:4509665632|{'env': 'test1'}    gdcty:4509713664|{'env': 'env'}    in mods.env # env.*形式引用env.py中的相同变量,对象id不变,gdctx内容发生变化

 

test2.py中通过env.*、global声明的形式修改全局变量取值:

# test2.py
from env import gx, gy, gdctx, gdcty
import env
from mods import print_env


env.gx = 'test2' # 通过env.gx引用值类型,相当于将env.gx指向新生成的值对象,env.gx对象id发生变化
env.gdctx = {'test2': 'test2'} # 通过env.gdctx引用引用类型,将其指向一个新的dict对象,env.gdctx对象id发生变化

def test():
    global gy, gdcty
    gy = 'test2' # 添加global声明后指向全局变量gy,将其指向新对象,gy对象id发生变化
    gdcty = {} # 指向新的dict对象,gdcty id发生变化

if __name__ == '__main__':
    test()
    print('gx:{}|{}\tgy:{}|{}\tgdctx:{}|{}\tgdcty:{}|{}\tin test2'.format(id(gx), gx, id(gy), gy, id(gdctx), gdctx, id(gdcty), gdcty))
    print('gx:{}|{}\tgy:{}|{}\tgdctx:{}|{}\tgdcty:{}|{}\tin test2.env'.format(id(env.gx), env.gx, id(env.gy), env.gy, id(env.gdctx), env.gdctx, id(env.gdcty), env.gdcty))
    print_env()

执行结果:

gx:4502704816|env    gy:4502704816|env    gdctx:4503685552|{'env': 'env'}        gdcty:4503733584|{'env': 'env'}    in env
gx:4502704816|env    gy:4504080752|test2  gdctx:4503685552|{'env': 'env'}        gdcty:4502670384|{}    in test2 # gx、gdctx保持import时的原值不受赢下,gy指向新值对象,gdcty指向新的dict对象
gx:4504080752|test2  gy:4502704816|env    gdctx:4502670144|{'test2': 'test2'}    gdcty:4503733584|{'env': 'env'}    in test2.env # env.gx、env.gdctx指向新的对象,env.gy、env.gdcy不受影响
gx:4502704816|env    gy:4502704816|env    gdctx:4503685552|{'env': 'env'}        gdcty:4503733584|{'env': 'env'}    in mods # 保持指向最开始import时的env.*对象,无任何变动
gx:4504080752|test2  gy:4502704816|env    gdctx:4502670144|{'test2': 'test2'}    gdcty:4503733584|{'env': 'env'}    in mods.env # env.gx、env.gdctx已经指向新的对象,env.gy、env.gdcty不变

通过test1.py、test2.py的执行结果可以得出以下结论:

1,from XX import YY的方式导入全局变量后,如果XX.YY取值在某一模块发生了修改导致其指向的对象发生了变化(对象id不同),其他模块引入的YY并不会同步修改,而是指向最初的取值。因而考虑全局变量修改的情况下,应使用import XX,而后使用XX.YY的方式进行引用。

2,值类型赋不同值肯定会导致对象id变化,因而无法跨文件传递修改内容,引用类型如果整体被指向新对象会导致对象id变化,同样无法跨文件传递修改,但是只修改引用对象本身的某部分内容则不会生成新对象,修改可以成功跨文件传递。

3,函数中引用全局变量,如果只是读取,会先查找同名本地变量,而后全局变量,但是如果涉及赋值、修改其语义则是定义一个新的局部变量,此时要记住使用global声明对应的全局变量。

 

转载请注明出处,原文地址:https://www.cnblogs.com/AcAc-t/p/python_global_import_rule.html

posted @ 2021-08-30 23:32  及时  阅读(8873)  评论(1编辑  收藏  举报