【问题标题】:Python class variable getting altered by changing instance variable that should just take its value通过更改应该只取其值的实例变量来更改 Python 类变量
【发布时间】:2016-09-23 10:28:29
【问题描述】:

我在初始化 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'])

被调用,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

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

【问题讨论】:

  • self.dataSheet[u'Item'] = self.MISSINGKEYS[u'Item'] 创建一个引用,因此当您更改它时,您可以在任何地方更改它。您需要创建一个副本self.dataSheet[u'Item'] = list(self.MISSINGKEYS[u'Item'])
  • 是的。这是关于 Python 的东西我不明白,99.9% 的时间你永远不需要有意识地创建任何东西的副本,一切都像你做按值调用一样工作,然后你突然绊倒了这样的事情.
  • 使用可变对象,您正在创建对可以更改的内容的引用,只有当对象是不可变的时才会创建一个新对象,如果使用i = 12; b = i; i += 12 b 进行了更改,那么仍然会为 12,因为 int 是不可变的,但具有可变结构,更改已就地完成,因此不会创建新对象。基本上,如果您想使用可变值/对象并且不只是想要引用,则需要根据对象进行复制或深度复制。
  • 谢谢,我想我现在明白了。
  • 不用担心,你也应该知道,即使对象本身是不可变的,如果它包含不可变对象,那么这些对象仍然可以更改,不可变对象仍然是同一个对象,即@987654342 @,这就是 deepcopy 的用武之地。

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


【解决方案1】:

这里:

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

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

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

制作副本。

【讨论】:

  • 谢谢,有帮助。
【解决方案2】:

从“按引用传递”与“按值传递”的角度考虑 Python 函数调用中发生的事情通常没有用处;有些人喜欢使用“通过对象”一词。请记住,Python 中的一切都是对象,因此即使您将整数传递给函数(在 C 术语中),您实际上也是在传递指向该整数对象的指针。

在你的第一个代码块中

self.X += 1

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

self.Y.append(1)

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-02-25
    • 1970-01-01
    • 1970-01-01
    • 2017-05-16
    • 2013-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多