最近碰到一個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