【问题标题】:python over-writing my list elementspython覆盖我的列表元素
【发布时间】:2012-11-09 01:58:36
【问题描述】:

对 python 很陌生,对 python 类很陌生。问题有点牵涉。非常感谢您的耐心:

我有一个班级“明星”。很简单。属性 x、v 和质量。另一个类 Galaxy 有一个属性“stars”,它只是一个星对象列表:

class Galaxy:

    numstars=0.
    stars=[]

    def __init__(self,numin,xes,vees,masses):
        self.numstars=numin
        for n in range(numin):
            self.stars.append(Star(xes[n],vees[n],masses[n]))

Galaxy 还有一个名为 time_stepper 的属性函数。可以说 time_stepper 只是更新“stars”的所有元素,然后返回“stars”:

def time_stepper(self,dt):
    self.velstep(dt)
    self.xstep(dt)
    self.velstep(dt)
    return(self.stars)

现在,我正在尝试驱动这个东西并将“stars”的各种更新存储在一个名为“history”的列表中:

gal=Galaxy(#stuff#)
history=[]
for n in range(100):
    history.append(gal.time_stepper(.1))

最后,我的问题:在这个循环的每次迭代中,新元素“星”被添加到“历史”中,但是......这里是...... 所有以前的历史元素都是重写并赋予与历史的最新元素相同的值! 那么发生了什么?我遇到了一些我以前不理解的关于 python 列表的事情,但我想我终于把它搞定了。显然不是。感谢您的帮助。

附录: 感谢大家的帮助。没想到会有这么多有用的回复,尤其是这么快。我的问题是我假设这两段代码本质上是相同的。第一:

>>> a=[]
>>> b=[1,2,3]
>>> a.append(b)
>>> b=[4,5,6]
>>> a.append(b)
>>> a
[[1, 2, 3], [4, 5, 6]]

第二:

>>> a=[]
>>> b=[1,2,3]
>>> a.append(b)
>>> b[:]=(4,5,6)
>>> b
[4, 5, 6]
>>> a.append(b)
>>> a
[[4, 5, 6], [4, 5, 6]]

哎呀!他们不是。所以在代码 1 中,我猜,b 被“重新指向”到一个全新的内存位置,而 a[0] 继续指向旧的 b。在第二个中,b 处的内存被“编辑”并且 a[0] 仍然指向该位置。追加后, a[1] 也指向该位置。我现在有吗?

我对 python 还很陌生,并且仍在研究“pythonic”哲学。但对我来说,直接重新分配指针,但以更复杂的方式完成“深拷贝”与我通常想要做的事情的方式有点倒退。任何人都可以启发我吗?再次感谢。

【问题讨论】:

  • 为什么numstarsstars是类变量而不是实例变量? (而且,如果您确实有充分的理由,为什么要立即将 numstars 类替换为 __init__ 函数中的实例 numstars?)
  • 没有看到Star中更新功能的代码,除了copy.deepcopy之外我很难推荐任何东西
  • Bob,你在最后一行代码中有语法错误。
  • @abarnet:你的问题的答案是我不知道我在做什么! :)
  • @inspectorG4dget: copy.deepcopy 显然是我需要的。谢谢。

标签: python list class


【解决方案1】:

我认为值得注意的是,如果您有超过 1 个星系,它们将共享相同的恒星。这可能会让你相信你正在覆盖你的星星,而实际上你并没有......

我猜你使用__init__ 可能会更好:

def __init__(self,numin,xes,vees,masses):
    self.stars = []
    self.numstars = numin
    for n in range(numin):
        self.stars.append(Star(xes[n],vees[n],masses[n]))

在这里,我将 class 属性 stars 转换为 instance 属性。现在每个实例都有自己的stars 列表,而不是与宇宙中的所有其他星系共享一个stars 列表。

正如其他人所指出的,您的history 列表将遇到类似的问题(您有多个引用同一个列表)。但是,修复实际上取决于您在 self.velstepself.xstep 中所做的事情。如果您修改了 Star 对象,那么简单(浅)列表副本对您没有任何好处(例如 gal.time_stepper(0.1)[:])。 (您将创建一个新列表,但它将包含不断更新的相同星星)。在这种情况下,当您追加到历史列表时,您将需要 copy.deepcopy

history=[]
for n in range(100):
    history.append(copy.deepcopy(gal.time_stepper(.1)))

【讨论】:

  • 这一切都很好……但它实际上并没有解决 OP 的问题。他仍然需要做history.append(gal.time_stepper(.1))[:],或者一些等效的方法来获得一个不同的对象。
  • @abarnert -- 是的,我意识到了这一点,但是我什至不认为制作浅拷贝可以做到这一点。我猜,但我敢打赌 OP 需要一个 copy.deepcopy,因为我的钱说 Star 对象被修改到位。
  • 你可能想通过拼写检查器来运行它……“galexy”和“mality”即使从快速列表中也会跳出来。但除此之外,一切都很好。
  • @arbarnert:是的。 copy.deepcopy 是诀窍。星星修改到位。谢谢。
【解决方案2】:

这是因为stars 列表是可变的——它是一个可变变量。当您调用stars.append 时,它不会创建新列表 - 它只是简单地编辑现有列表。当您调用 history.append(x) 时,python 不会创建 x 的全新、干净的副本 - 它假定您希望将“真实的”x 放置在 history 数组中。

如果您想在每次迭代时复制列表的状态,有多种选择(请参阅How to clone a list in python?)。

copy module 可以解决问题。它同时提供“浅”和“深”副本——粗略地说,深副本试图复制他们在对象中找到的任何其他变量(例如,包含其他列表的列表),而浅副本只向下一层:

这是copy的浅拷贝:

import copy

history=[]
for n in range(100):
    history.append(copy.copy(gal.time_stepper(.1)))

还有深拷贝:

import copy

history=[]
for n in range(100):
    history.append(copy.deepcopy(gal.time_stepper(.1)))

切片数组会起作用,因为它总是将浅拷贝作为中间步骤:

history = []
for n in range(100):
    history.append(gal.time_stepper(.1)[:])

您也可以在现有列表上调用list - 这种类型转换式的操作总是返回一个新的列表对象(同样,浅层):

history = []
for n in range(100):
    history.append(list(gal.time_stepper(.1)))

【讨论】:

  • 为什么使用copy.copy 而不仅仅是[:]
  • @abarnert 可读性。 [:] 在我看来属于“整洁的 Python 技巧”类别。
  • 这是一个标准的成语,在讨论这个问题的教程部分和官方常见问题解答中都使用了它,所以避免它是非pythonic。
  • 但是,在答案提示中引入copy.copy 提示copy 和随后的copy.deepcopy,这可能对未来的OP 有所帮助。
  • @abarnert 我想我不同意这些文档中的用法。当 Alex Martelli 说“这是一种奇怪的语法并且永远使用它没有意义”时,我和 Alex Martelli 在一起。 (stackoverflow.com/questions/2612802/…)
【解决方案3】:

它不是更改或重写所有值,而是历史记录中的所有元素都指向同一个数组对象。您需要按值分配,而不是使用数组的副本进行引用。

【讨论】:

    猜你喜欢
    • 2016-02-01
    • 2014-01-01
    • 1970-01-01
    • 2018-03-15
    • 1970-01-01
    • 2020-08-28
    • 2023-02-08
    • 1970-01-01
    • 2020-11-07
    相关资源
    最近更新 更多