在函数范围内修改的字典值也会在外部进行修改,而不使用全局变量



我不会提供一个可复制的例子,因为对于这个特定的问题,我不能用一个小的伪例子来复制这个问题。然而,我相信发生了一些奇怪的事情,对我来说,这不是一个简单的拼写错误。也许这个问题会敲响警钟,也许有人会有一个解释。

让我们考虑一下我的项目的3个文件:

cli/main.py
main.py
nfb.py

main.py中,设置字典是从.json文件加载的。这个字典中的一个特定值是settings = {'ONLINE':{'APPLY_REJECTION': True}}。然后,它从cli/main.py文件中调用main函数,该文件的参数是设置字典。

main.py

from cli.main import main 
settings = load_settings(fname)
main(settings)

正如您可能从首字母缩写CLI中猜到的那样,调用的函数是一个简单的命令行界面,在该界面中,用户可以获得X种可能性(例如,键从1到9的9(,并根据输入调用另一个函数。然后重复输入阶段,直到按下退出键,例如0。

cli/main.py

def main(settings):
while True:
selection = input_menu(options)
if selection == 0:
break # exit key
elif selection == 1
x, y = nfb.run(a, b, c, d, settings)

这是非常示意性的。显然,在我的代码中,对输入进行了错误检查,定义了变量。。。然而,需要注意的一点是,字典settings是从nfb.py文件传递给run函数的。

nfb.py中,函数运行可能会将变量settings['ONLINE']['APPLY_REJECTION']从True修改为False。一旦函数退出,程序就在等待新的用户输入(选择(,因为我们已经离开了nfb.run()的范围,变量settings['ONLINE']['APPLY_REJECTION']应该会返回True,以便将来调用nfb.run()

然而,这并不是我所观察到的。变量settings['ONLINE']['APPLY_REJECTION']保持为False,在nfb.run()的范围之外。

更令人惊讶的是,我在nfb.py:中尝试过这样做

import copy
def run(a, b, c, d, settings):
backup_settings = copy.deepcopy(settings)

# do stuff
if condition:
settings['ONLINE']['APPLY_REJECTION'] = False
# restore settings
settings = backup_settings 

通过几次打印,我可以确认在# restore settings之前,变量settings['ONLINE']['APPLY_REJECTION']被设置为False,在函数的最后一行settings = backup_settings之后,变量settings['ONLINE']['APPLY_REJECTION']被设置为True。

但是,如果在CLI的while循环中,我在选择(即用户输入(之前或之后打印相同的变量,我可以看到,在nfb.run()函数中将该变量设置为False后,该变量仍设置为False,尽管有备份/恢复行!

cli/main.py

def main(settings):
while True:
print (settings['ONLINE']['APPLY_REJECTION'])
selection = input_menu(options)
if selection == 0:
break # exit key
elif selection == 1
x, y = nfb.run(a, b, c, d, settings)

上面的代码将第一次打印True,一旦nfb.run()将变量设置为False,则打印False。

需要注意的是,绝对没有其他地方可以修改这个变量。此外,我对该问题的解决方案是在run开始时从字典中提取变量,然后使用该提取变量,从而避免更改设置字典中的任何内容。

解决方法:

def run(a, b, c, d, settings):
apply_rejection = settings['ONLINE']['APPLY_REJECTION']

# do stuff
if condition:
apply_rejection = False

这个变通方法有效(没有其他更改!(,并表明应用于此变量settings['ONLINE']['APPLY_REJECTION']的修改是nfb.run()中的修改,而且正如我所声称的,没有其他行直接干扰任何地方的设置字典。

讨论:

在我看来,在我最初的方法中,nfb.run()内部的settings字典指向与外部的对象相同的对象,作为cli.main(settings)函数的参数加载。更令人惊讶的是,一旦nfb.run()中的变量settings被重新分配了一个新值(在我的情况下是原始对象的深度副本(,这种联系似乎就被打破了。

有人关注过这篇长文,对这种行为有想法、有解释吗?

spam = {'foo':'bar'}
def eggs(arg):
arg['foo'] = 'baz'
print(spam)
eggs(spam)
print(spam)

输出

{'foo': 'bar'}
{'foo': 'baz'}

dict是可变类型。我分享的链接准确地解释了这一点,尽管那里的示例具有不同的可变类型list

最新更新