Python函数“记住”早期参数(**kwargs)
我有一些对象,它们有一个属性字典,叫做 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 个回答
attrs
是一个指向字典的 引用。当你创建一个新对象时,self.attrs
就指向那个字典。当你从 kwargs
中赋值时,这个值会放到这个字典里。
现在,当你创建第二个实例时,它的 self.attrs
也指向同一个字典。因此,它会获取到这个字典里的所有数据。
想了解更多,可以看看 “Python中的最小惊讶原则:可变的默认参数” 这篇讨论。此外,还可以查看 Python中的默认参数值 在 effbot 上的内容。
那如果每次都创建一个新的字典呢?
class Thing:
def __init__(self, attrs=None, **kwargs):
self.attrs = attrs or {}
self.attrs.update(kwargs)
使用默认参数的问题在于,实际上只有一个实例存在。当你在 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,就能完全避免这个问题。