通过更改应该只接受其值的实例变量来改变Python类变量

时间:2016-09-23 10:28:29

标签: python python-2.7 class pass-by-reference

我在初始化Python类时遇到了一种奇怪的效果。不确定我是否忽视了一些明显的东西。

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

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是一个类变量,但它没有被故意改变。

在第一次通话中,课程进入这一行:

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

显然开始了这一切。显然,我只希望self.dataSheet[u'Item']获取self.MISSINGKEYS[u'Item']的值,而不是成为对它的引用或类似内容。

在以下两个中调用

self.dataSheet[u'Item'].append(stuff[u'Item'])
而是调用

append适用于self.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

有人可以向我解释这里发生了什么以及如何避免它吗?

编辑: 我试过这个:

/foo

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

2 个答案:

答案 0 :(得分:1)

下面:

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

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

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

制作副本。

答案 1 :(得分:1)

思考Python函数调用中发生的事情"通过引用传递" vs"通过价值"通常不是很有用;有些人喜欢使用术语"通过对象"。请记住,Python中的所有内容都是一个对象,因此即使将一个整数传递给一个函数(在C术语中),您实际上也会将指针传递给该整数对象。

在您的第一个代码块中执行

self.X += 1

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

然而,

self.Y.append(1)

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

这样的任务
self.Y = ['new stuff']

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

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