Python函数“记住”早期参数(**kwargs)

7 投票
5 回答
2925 浏览
提问于 2025-04-16 22:05

我有一些对象,它们有一个属性字典,叫做 obj.attrs。这些对象的构造函数可以接受一个字典和/或 **kwargs,这样使用起来更方便。

大概是这个样子的:

class Thing:
    def __init__(self, attrs={}, **kwargs):
        for arg in kwargs:
            attrs[arg] = kwargs[arg]
        self.attrs = attrs

也就是说,Thing({'color':'red'})Thing(color='red') 是一样的效果。

我的问题是,构造函数似乎会“记住”最后一次传入的 attrs 值。

举个例子:

>>> thing1 = Thing(color='red')
>>> thing2 = Thing()
>>> thing2.attrs
{'color': 'red'}

...但是 thing2.attrs 应该只是一个空字典! {}

这让我想知道,使用 两者 **kwargs 和像 attrs={} 这样的参数是不是会有问题。

有没有什么想法呢?

5 个回答

2

attrs 是一个指向字典的 引用。当你创建一个新对象时,self.attrs 就指向那个字典。当你从 kwargs 中赋值时,这个值会放到这个字典里。

现在,当你创建第二个实例时,它的 self.attrs 也指向同一个字典。因此,它会获取到这个字典里的所有数据。

想了解更多,可以看看 “Python中的最小惊讶原则:可变的默认参数” 这篇讨论。此外,还可以查看 Python中的默认参数值effbot 上的内容。

2

那如果每次都创建一个新的字典呢?

class Thing:
    def __init__(self, attrs=None, **kwargs):
        self.attrs = attrs or {}
        self.attrs.update(kwargs)
14

使用默认参数的问题在于,实际上只有一个实例存在。当你在 init 方法的定义中写 attrs={} 时,这个单一的默认空字典 {} 就是每次调用这个方法时使用的默认值(它不会每次都创建一个新的空字典,而是使用同一个)。

问题是,如果只有一个 attrs 实例存在,然后对于每个 Thing 的实例你都写 self.attrs = attrs,那么每个实例的 self.attrs 变量实际上都指向同一个共享的默认 attrs 实例。

另一个问题是,这样做不是完全多余吗?你可以使用 **kwargs 来传递关键字/值参数或者一个字典。如果你只是这样定义:

class Thing:
    def __init__(self, **kwargs):
        for arg in kwargs:
            self.attrs[arg] = kwargs[arg]

所有这些方法仍然有效:

thing1 = Thing(color='red')

thing2 = Thing(**{'color':'red'})

my_dict = {'color' : 'red'}
thing3 = Thing(**my_dict)

所以如果你简单地这样定义和使用 Thing,就能完全避免这个问题。

撰写回答