为什么构造新对象可能会改变该类的每个*实例的字段?

时间:2016-08-23 18:06:16

标签: python object dictionary immutability

以下是相关代码的简化版本:

class thing:
    def __init__(self, data1, data2={'foo': 1}):
        self.data2 = data2
        self.data2['bar'] = data1

datas = ['FIRST', 'SECOND']
things = [thing(x) for x in datas]
for p in things:
    print p.data2['bar']

我希望期望此代码返回:

FIRST
SECOND

然而,实际上会返回:

SECOND
SECOND

为什么?

我最好的猜测是:

  • 我正在创建一个字典ur_dict = {'foo': 1}
  • 当我初始化类thing的对象时,我创建新词典self.data2={'foo': 1},而是初始化对ur_dict的引用,
  • 并且在构造函数中,我将键值对bar: data1添加到self.data2时,我实际上是将该键和值添加到ur_dict本身。这将更新列表中每个对象的data2字段。

我通过将以下代码段附加到上面的代码来测试这个假设:

things[0].data2['bar'] = 'FIRST'
for p in things:
    print p.data2['bar']

果然,结果是:

FIRST
FIRST

所以我认为我的假设可能是正确的,但对我来说仍然是个谜。我的问题似乎与this问题非常相关---当我在构造函数中创建默认值时,这个值是 class 变量而不是实例变量吗?为什么python不为对象中的参数的默认值创建新对象?是否有一种很好的方法来概念化或在将来发现这种错误?有什么好的参考资料来了解这里发生了什么?

(如果我用行话来修改术语,请提前道歉;我是正规教育的数学家。)

编辑:@CrazyPython[::-1]提到这是函数的属性,而不是类或对象。我创建了一个带有可变默认参数的函数示例,我试图弄清楚如何以我在上面的对象和类中遇到的方式来打破它。想出了这个:

EXPONENT = 1
def fooBar(x, y=EXPONENT):
    return x**y
print EXPONENT
print foo(5)
EXPONENT = 2
print EXPONENT
print foo(5)

然而,它返回:

1
5
2
5

什么是“打破”这个以解释为什么不在这里使用可变默认参数的好方法?

1 个答案:

答案 0 :(得分:2)

self.data2={'foo': 1}是一个可变的默认参数。永远不要那样做。 99%的时间都是错误的。

def foo(a, b={}):
    ...

不等于

def foo(a, b=None):
    if b is None:
        b = {}
每次都不会重建{p> b。这是同一个目标。

  

将来是否有一种很好的方法来概念化或捕捉这种错误?

使用PyCharm或其他好的编辑器

  

当我在构造函数中创建默认值时,此值是一个类变量而不是实例变量吗?

不不不不不不。它与函数有关,而不是类或实例。这种行为适用于所有功能。它与课程或您的链接问题无关。

  

为什么python不为对象中的参数默认值创建新对象?

性能和兼容性。构造对象是一种可能很昂贵的操作。

附录

  • 请使用CamelCase进行课程学习。不这样做是违反PEP 8,这是Python官方风格指南。
  • 得到一个体面的编辑。就个人而言,我推荐PyCharm。 (我不是推销员)它是免费的。
    • PyCharm可以重命名变量,突出显示PEP 8错误,执行高级自动完成,类型检查,发现可变默认参数等等。
  • 您不是继承objectThat's bad.*

      

    为什么这个python代码会改变列表中每个对象的字段?

  • 没有上下文,这是一个糟糕的标题。它询问代码。当我们阅读标题时,我们没有代码。

让它更清晰

声明函数时,将计算默认参数。 (不是在执行时,当它被陈述时)它们与功能相关联。执行函数时,不会生成默认参数的副本。您可以直接修改默认参数。调用的下一个函数将接收相同的对象,并进行修改。

<子> *你似乎是一名教授或类似的人。请不要被模因冒犯。这只是从互联网上获得建议的风险。