Python:为什么我的列表在从对象中检索后会发生变化

时间:2010-09-08 00:34:52

标签: python

简单的问题,我已经缩减了一个问题,当我向对象追加更多数据时,我从一个对象检索的列表正在改变。不在列表中。

任何人都可以帮助我理解python的行为吗?

class a():
    def __init__(self):
        self.log = []
    def clearLog(self):
        del self.log[:]
    def appendLog(self, info):
        self.log.append(str(info))
    def getLog(self):
        return self.log

if __name__ == '__main__':
    obj = a()
    obj.appendLog("Hello")

    # get an instance as of this moment....
    list = obj.getLog()
    print list

    obj.appendLog("World")

    # print list, BUT we want the instance that was obtained
    # before the new appendage.   
    print list

输出:

['Hello']
['Hello', 'World']

5 个答案:

答案 0 :(得分:4)

编码时

`list = obj.getLog()`

(忽略 - 只是一秒钟 - 可怕的想法是使用影子内置的标识符!!!)你说:“make name list refer与obj.getLog()返回的完全相同的对象 - 我们从class a的代码中知道的是obj.log。所以当然从现在开始你有一个带有两个名字的列表对象,当你通过 名称改变那个对象时,所有的改动都会从两个名字中完全可见 - 当然 - 请记住,只是一个对象,你只是为它使用多个名字!你从来没有要过副本,所以当然Python没有副本。

当您需要副本而不是原始副本时,要求一个!当你知道你需要的类型(这里是一个列表)时,最好的方法是调用类型,即:

mylist = list(obj.getLog())

如果您选择使用您的标识符遍布内置组件,这当然变得不可能了 - 这是为什么这样的标识符选择是 BAD 的想法的一个很好的部分(我不能压力足够:很难想象在Python编码中使用的任何更糟糕的样式选择,而不是这样的命名)。所以,我已将标识符重命名为mylist(当然,您需要在两个print语句中重命名它。)

可以使用高度不可读或较慢的方法来弥补肆意破坏内置标识符list的正常功能,当然 - 例如:

import copy
list = copy.copy(obj.getLog())   # somewhat slower

list = obj.getLog()[:]           # traditional, but ECCH

temp = obj.getLog()
list = type(temp)(temp)          # abstruse

BY FAR 最简单,最干净,最推荐的方法是 NOT 将您的标识符命名为与Python内置函数相同(避免命名也是一个不错的主意)它们就像标准Python库中的模块一样,尽管有一些较弱的原因。)

答案 1 :(得分:3)

您创建新列表的唯一位置是在构造函数中,语句为:

self.log = []

稍后,当你这样做时:

list = obj.getLog()

只需在新变量中引用相同的列表(注意,不要使用list 作为变量名称,因为它会影响类型)。它不以任何方式创建或克隆列表。如果要克隆它,请执行:

def getLog(self):
    return list(self.log)

如果合适,您还可以使用元组(只读序列):

def getLog(self):
    return tuple(self.log)

这可能有助于最大限度地减少应该修改的混淆。

答案 2 :(得分:1)

看看这个方法:

def getLog(self):
        return self.log

您已返回对self.log的引用并将其分配给列表。现在他们都指向堆上的相同列表。当您更改self.log时,列表指向内存中的相同位置。

你必须制作一个克隆并将其分配给列表,以使两者独立。

答案 3 :(得分:1)

对象通过引用在Python中传递 - Python不会为您制作副本。这条线

return self.log

返回list内部使用的obj对象的引用。这意味着在行

之后
list = obj.getLog()

您的list变量引用与<{1}}相同的相同对象。要获取副本,请使用Python的切片语法:

obj.log

答案 4 :(得分:0)

这归结为python和ruby等语言试图让人们更容易引用任意的,复杂的对象 - 关联容器列表的关联容器等。这些变得太复杂而无法重复索引(而且速度很慢),因此脚本编写者希望能够说出

galleries = world['asia']['japan'][3]['Tokyo']['Galleries']

然后针对进行查询或更新东京的画廊。这有时很方便(在进行更新时),有时候 - 就像你认为你已经制作了一些数据的私人副本而想要按摩一个而不影响另一个 - 这非常令人困惑和不方便。但是,大多数脚本语言都采用相同的策略,因为每次只需要一个“句柄”来检查较大数据结构的某些部分的常见情况,每次都会非常缓慢地制作完全独立的数据副本。它处理基本类型(如数字和文本字符串)的方式很难看并且不一致。正如其他人所说,你需要使用:

new_list = original_list[:] # slice off a complete copy of a list
new_dictionary = original_dictionary.copy()  # for dictionaries