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

2024-04-30 02:04:51 发布

您现在位置:Python中文网/ 问答频道 /正文

使用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)),这种修改可能是一个问题?


Tags: 函数name参数if字典demomaindef
3条回答

它总是安全的。作为spec says

If the form “**identifier” is present, it is initialized to a new ordered mapping receiving any excess keyword arguments, defaulting to a new empty mapping of the same type.

增加了强调。

您总是可以保证在可调用的内部得到一个新的映射对象。看这个例子

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!'

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

对于Python级别的代码,函数中的kwargsdict将始终是一个新的dict

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

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

会产生'0',而不是引起KeyError

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

以上两个答案都是正确的,说明技术上,变异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

从技术上讲,这回答了您的问题,因为共享对mutablekwargs的引用确实会导致超出函数作用域的效果

在生产代码中,我已经多次被这个问题困扰,现在,无论是在我自己的代码中,还是在审查其他代码时,我都明确地注意到了这个问题。这个错误在我上面设计的例子中很明显,但是在创建共享一些公共选项的工厂函数时,它在实际代码中更隐蔽。

相关问题 更多 >