Python class 变量通过更改应该只取其值的实例变量而改变

Python class variable getting altered by changing instance variable that should just take its value

初始化 Python class 时,我遇到了奇怪的效果。不确定我是否忽略了一些明显的东西。

首先,我知道传递给 classes 的列表显然是通过引用传递的,而整数是通过值传递的,如本例所示:

class Test:
  def __init__(self,x,y):
    self.X = x
    self.Y = y
    self.X += 1
    self.Y.append(1)

x = 0
y = []
Test(x,y)
Test(x,y)
Test(x,y)
print x, y

产生结果:

0 [1, 1, 1]

到目前为止一切顺利。现在看这个例子:

class DataSheet:
  MISSINGKEYS = {u'Item': ["Missing"]}

  def __init__(self,stuff,dataSheet):
    self.dataSheet = dataSheet
    if self.dataSheet.has_key(u'Item'):
      self.dataSheet[u'Item'].append(stuff[u'Item'])
    else:
      self.dataSheet[u'Item'] = self.MISSINGKEYS[u'Item']

这样称呼

stuff = {u'Item':['Test']}
ds = {}
DataSheet(stuff,ds)
print ds
DataSheet(stuff,ds)
print ds
DataSheet(stuff,ds)
print ds

产量:

{u'Item': ['Missing']}
{u'Item': ['Missing', ['Test']]}
{u'Item': ['Missing', ['Test'], ['Test']]}

现在让我们打印 MISSINGKEYS

stuff = {u'Item':['Test']}
ds = {}
DataSheet(stuff,ds)
print DataSheet.MISSINGKEYS
DataSheet(stuff,ds)
print DataSheet.MISSINGKEYS
DataSheet(stuff,ds)
print DataSheet.MISSINGKEYS

这产生:

{u'Item': ['Missing']}
{u'Item': ['Missing', ['Test']]}
{u'Item': ['Missing', ['Test'], ['Test']]}

完全相同的输出。为什么?

MISSINGKEYS 是一个 class 变量,但绝不会被故意更改。

在第一次调用中,class 进入这一行:

self.dataSheet[u'Item'] = self.MISSINGKEYS[u'Item']

这显然是一切的开始。显然我只想 self.dataSheet[u'Item']self.MISSINGKEYS[u'Item'] 的值,而不是成为对它的引用或类似的东西。

在下面的两个调用行

self.dataSheet[u'Item'].append(stuff[u'Item'])

被调用,appendself.dataSheet[u'Item']self.MISSINGKEYS[u'Item'] 上工作,它不应该。

这导致假设在第一次调用后两个变量现在都引用同一个对象。

然而,尽管他们不平等:

ds == DataSheet.MISSINGKEYS
Out[170]: True
ds is DataSheet.MISSINGKEYS
Out[171]: False

谁能给我解释一下这是怎么回事,我该如何避免?

编辑: 我试过这个:

ds[u'Item'] is DataSheet.MISSINGKEYS[u'Item'] 
Out[172]: True

好吧,两个词典中的这个条目引用了同一个对象。我怎样才能改为分配值?

这里:

 else:
  self.dataSheet[u'Item'] = self.MISSINGKEYS[u'Item']

您正在使用作为 MISSINGKEYS['Item'] 值的列表设置 dataShee['Item']相同的列表。尝试

 else:
  self.dataSheet[u'Item'] = list(self.MISSINGKEYS[u'Item']) 

制作副本。

根据 "pass by reference" 和 "pass by value" 思考 Python 函数调用中发生的情况通常没有用;有些人喜欢使用 "pass by object" 这个词。请记住,Python 中的所有内容都是对象,因此即使将整数传递给函数(用 C 术语),您实际上也是在传递指向该整数对象的指针。

在你的第一个代码块中

self.X += 1

这个 不会 修改绑定到 self.X 的当前整数对象。它创建一个具有适当值的新整数对象,并将该对象绑定到 self.X 名称。

self.Y.append(1)

改变绑定到self.Y的当前列表对象,它恰好是传递给Test.__init__的列表对象作为它的y参数。这与调用代码中的 y 列表对象相同,因此当您修改 self.Y 时,您正在更改调用代码中的 y 列表对象。 OTOH,如果你做了像

这样的作业
self.Y = ['new stuff']

那么名称 self.Y 将绑定到新列表,而旧列表(在调用代码中仍绑定到 y)将不受影响。

您可能会发现这篇文章很有帮助:Facts and myths about Python names and values,由 SO 资深人士 Ned Batchelder 撰写。