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

时间:2017-08-25 14:28:09

标签: python function dictionary scope

使用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}

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

3 个答案:

答案 0 :(得分:55)

总是安全的。作为spec says

  

如果表单“** identifier”存在,则会将其初始化为 new   有序映射接收任何多余的关键字参数,默认为   相同类型的空映射。

强调增加。

始终保证在callable中获取新的mapping-object。见这个例子

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

kwargs = {'foo': 'bar'}
print(id(kwargs))
# 140185018984344
f(**kwargs)
# (140185036822856, {'foo': 'bar'})

因此,虽然f可能会修改通过**传递的对象,但它无法修改调用者的** - 对象本身。

更新:由于您询问了角落案例,这里有一个特殊的地狱,实际上可以修改来电者kwargs

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

kwargs = {}
kwargs['recursive!'] = kwargs
f(**kwargs)
assert kwargs['recursive!'] == 'Look ma, recursive!'

但是你可能不会在野外看到它。

答案 1 :(得分:13)

对于Python级代码,函数内的kwargs dict将始终是一个新的字典。

对于 C扩展,请注意。 {API}版本的kwargs有时会直接通过dict。在以前的版本中,它甚至会直接传递dict子类,导致bug(now fixed)其中

'{a}'.format(**collections.defaultdict(int))

会生成'0',而不是提出KeyError

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

答案 2 :(得分:2)

上述两个答案都是正确的,从技术上说,变异kwargs永远不会对父作用域产生影响。

但是...... 这不是故事的结尾引用kwargs可以在函数范围之外共享,然后您遇到所有常见的共享变异状态问题,这些问题都是您期望的。

def create_classes(**kwargs):

    class Class1:
        def __init__(self):
            self.options = kwargs

    class Class2:
        def __init__(self):
            self.options = kwargs

    return (Class1, Class2)

Class1, Class2 = create_classes(a=1, b=2)

a = Class1()
b = Class2()

a.options['c'] = 3

print(b.options)
# {'a': 1, 'b': 2, 'c': 3}
# other class's options are mutated because we forgot to copy kwargs

从技术上讲,这回答了你的问题,因为共享对mutable kwargs的引用确实会导致函数范围之外的影响。

我已经在生产代码中被多次咬过了,这是我现在明确注意到的,无论是在我自己的代码中还是在审查其他代码时。在我上面的设计示例中,这个错误显而易见,但在创建具有共同选项的工厂函数时,它在实际代码中更加狡猾。