zoukankan      html  css  js  c++  java
  • 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:{}|{}	gy:{}|{}	gdctx:{}|{}	gdcty:{}|{}	in 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:{}|{}	gy:{}|{}	gdctx:{}|{}	gdcty:{}|{}	in mods'.format(id(gx), gx, id(gy), gy, id(gdctx), gdctx, id(gdcty), gdcty))
        print('gx:{}|{}	gy:{}|{}	gdctx:{}|{}	gdcty:{}|{}	in 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:{}|{}	gy:{}|{}	gdctx:{}|{}	gdcty:{}|{}	in test1'.format(id(gx), gx, id(gy), gy, id(gdctx), gdctx, id(gdcty), gdcty))
        print('gx:{}|{}	gy:{}|{}	gdctx:{}|{}	gdcty:{}|{}	in 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:{}|{}	gy:{}|{}	gdctx:{}|{}	gdcty:{}|{}	in test2'.format(id(gx), gx, id(gy), gy, id(gdctx), gdctx, id(gdcty), gdcty))
        print('gx:{}|{}	gy:{}|{}	gdctx:{}|{}	gdcty:{}|{}	in 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

    签名:拥抱开源,拥抱自由
  • 相关阅读:
    sql注入之payload
    cve2019-0708漏洞复现
    xss学习
    kernel panic not syncing
    nodeJS爬虫
    JS刷题自制参考知识
    HTML Cookie
    点击按钮触发div颜色改变的几种写法
    Bootstrap4布局(简要)
    jQuery实现论坛发帖Demo
  • 原文地址:https://www.cnblogs.com/AcAc-t/p/python_global_import_rule.html
Copyright © 2011-2022 走看看