如何在不修改基类可变默认参数的情况下更新其创建的属性?

15 投票
3 回答
3756 浏览
提问于 2025-04-15 14:01

我发现了一个关于子类和字典更新的奇怪问题,涉及到新式类:

Python 2.6.2 (r262:71605, Apr 14 2009, 22:40:02) [MSC v.1500 32 bit (Intel)] on
win32
>>> class a(object):
...     def __init__(self, props={}):
...             self.props = props
...
>>> class b(a):
...     def __init__(self, val = None):
...             super(b, self).__init__()
...             self.props.update({'arg': val})
...
>>> class c(b):
...     def __init__(self, val):
...             super(c, self).__init__(val)
...
>>> b_inst = b(2)
>>> b_inst.props
{'arg': 2}
>>> c_inst = c(3)
>>> c_inst.props
{'arg': 3}
>>> b_inst.props
{'arg': 3}
>>>

在调试时,在第二次调用(c(3))时,你可以看到在a的构造函数中,self.props已经等于{'arg': 2},而当b的构造函数被调用后,它变成了{'arg': 3},这两个对象都是这样!

而且,构造函数的调用顺序是:

  a, b    # for b(2)
  c, a, b # for c(3)

如果你把self.props.update()换成self.props = {'arg': val}b的构造函数中,事情就会正常运作,表现也会如预期。

但是我真的需要更新这个属性,而不是替换它。

3 个回答

3

简短的说法:这样做:

class a(object):
    def __init__(self, props=None):
        self.props = props if props is not None else {}

class b(a):
    def __init__(self, val = None):
        super(b, self).__init__()
        self.props.update({'arg': val})

class c(b):
    def __init__(self, val):
    super(c, self).__init__(val)

详细说明:

函数的定义只会被计算一次,所以每次你调用这个函数时,使用的都是同一个默认参数。为了让它像你想的那样工作,默认参数应该在每次调用函数时都被计算。但实际上,Python只会生成一个函数对象,并把默认参数添加到这个对象里(就像 func_obj.func_defaults 这样)。

8

你的问题出在这一行:

def __init__(self, props={}):

{} 是一种可变类型。在 Python 中,默认参数的值只会被计算一次。这意味着你所有的实例都在共享同一个字典对象!

要解决这个问题,可以把它改成:

class a(object):
    def __init__(self, props=None):
        if props is None:
            props = {}
        self.props = props
19

props 不应该像那样设置默认值。可以这样做:

class a(object):
    def __init__(self, props=None):
        if props is None:
            props = {}
        self.props = props

这是一个常见的 Python “陷阱”

撰写回答