修改`** 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共享一些常用选项的工厂函数时,它在实际代码中显得太偷偷摸摸了。