修改`**kwargs`字典是否总是安全的?

Pau*_*aul 48 python dictionary scope function

使用Python函数语法def f(**kwargs),在函数kwargs中创建一个关键字参数字典,并且字典是可变的,所以问题是,如果我修改kwargs字典,是否有可能在函数范围之外产生一些影响?

根据我对字典解包和关键字参数打包如何工作的理解,我认为没有理由相信它可能不安全,而且在我看来Python 3.6中没有这种危险:

def f(**kwargs):
    kwargs['demo'] = 9

if __name__ == '__main__':
    demo = 4
    f(demo=demo)
    print(demo)     # 4

    kwargs = {}
    f(**kwargs)
    print(kwargs)   # {}

    kwargs['demo'] = 4
    f(**kwargs)
    print(kwargs)    # {'demo': 4}
Run Code Online (Sandbox Code Playgroud)

但是,这是特定于实现的,还是它是Python规范的一部分?我是否忽略了任何情况或实现(除非修改参数本身是可变的,如此kwargs['somelist'].append(3))这种修改可能是一个问题?

use*_*968 57

它总是安全的.正如规范所说

如果存在"**标识符"形式,则将其初始化为 接收任何多余关键字参数的有序映射,默认为相同类型的空映射.

强调补充说.

始终保证在可调用内部获取新的映射对象.看这个例子

def f(**kwargs):
    print((id(kwargs), kwargs))

kwargs = {'foo': 'bar'}
print(id(kwargs))
# 140185018984344
f(**kwargs)
# (140185036822856, {'foo': 'bar'})
Run Code Online (Sandbox Code Playgroud)

因此,尽管f可以修改通过的对象**,但它无法修改调用者的对象**本身.


更新:既然你问过角落的情况,这里有一个特殊的地狱,实际上修改了调用者kwargs:

def f(**kwargs):
    kwargs['recursive!']['recursive!'] = 'Look ma, recursive!'

kwargs = {}
kwargs['recursive!'] = kwargs
f(**kwargs)
assert kwargs['recursive!'] == 'Look ma, recursive!'
Run Code Online (Sandbox Code Playgroud)

不过,你可能不会在野外看到这一点.

  • @Paul - 的确,不需要任何像自我引用词典那样充满异国情调的东西.如果输入字典的一个元素是可变的(如列表)并且该元素在函数内部变异(例如使用`.append()`),则输入字典将被突变. (9认同)
  • 我会说角落案例是"论证本身是可变的"的一个特例,但它仍然是聪明的. (4认同)

use*_*ica 14

对于Python级代码,kwargs函数内的字典总是一个新的字典.

但是对于C扩展,请注意.C API版本kwargs有时会直接通过dict.在以前的版本中,它甚至会直接通过dict子类,导致bug(现在已修复)在哪里

'{a}'.format(**collections.defaultdict(int))
Run Code Online (Sandbox Code Playgroud)

会产生'0'而不是提高KeyError.

如果您必须编写C扩展(可能包括Cython),请不要尝试修改kwargs等效项,并注意旧Python版本上的dict子类.

  • @Paul:"有时"出于以下原因:1)据我所知,这是一个未记录的实现细节,我不知道某些CPython版本是否表现不同,或者未来的版本是否会改变行为.2)dict子类修复意味着某些技术上的对象(特别是子类实例)不会直接传递.3)如果你执行类似`c_func(a = 1,**{b:2})`的操作,将创建一个新的字典. (2认同)