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

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

从我对字典解包和关键字参数包装工作的理解来看,我没有看到任何理由相信这可能是不安全的,在我看来,在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) )这种修改可能是一个问题?

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

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

重点补充。

您始终保证在可调用内部获得新的映射对象。 看到这个例子

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

所以,虽然f可能会修改通过**传递的对象,但它不能修改调用者的**本身。


更新 :既然你问了关于angular落案件,这是一个特别的地狱,你实际上修改了调用者的kwargs

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

不过,你可能不会在野外看到。

对于Python级别的代码,函数内的kwargs字典将永远是一个新的字典。

不过,对于C扩展 ,小心。 kwargs的C API版本有时会直接传递一个字典。 在以前的版本中,它甚至会直接通过dict子类,导致错误( 现在已修复 )在哪里

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

会产生'0'而不是引发KeyError

如果你必须编写C扩展(可能包括Cython),不要试图修改kwargs等价物,并且要注意旧Python版本中的dict子类。

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

但是… 这不是故事的结尾 。 可以在函数作用域之外共享kwargs引用 ,然后运行您所期望的所有常见的共享变异状态问题。

 def create_objs(**kwargs): class Class1: def __init__(self): self.options = kwargs class Class2: def __init__(self): self.options = kwargs return (Class1, Class2) Class1, Class2 = create_objs(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的引用确实会导致函数作用域之外的效果。

在生产代码中,我被多次咬了这个东西,这是我现在明确注意的,无论是在我自己的代码中,还是在审查其他代码时。 在我上面所做的例子中,这个错误是显而易见的,但是在创build共享一些常用选项的工厂函数时,它在实际代码中显得太偷偷摸摸了。