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

Posted To be a better programmer

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

签名:拥抱开源,拥抱自由

以上是关于Python中import外部模块全局变量修改规则及踩坑的主要内容,如果未能解决你的问题,请参考以下文章

Python 函数内变量的作用域

python关于import的汇总

Pyhton 模块和包

Python 之 模块

Python入门-5函数:10nonlocal-global-LEGB规则

python中各模块变量共享的问题。