0

对 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”哲学。但对我来说,直接重新分配指针,但以更复杂的方式完成“深拷贝”与我通常想要做的事情的方式有点倒退。任何人都可以启发我吗?再次感谢。

4

3 回答 3

5

我认为值得注意的是,如果你有超过 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]))

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

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

history=[]
for n in range(100):
    history.append(copy.deepcopy(gal.time_stepper(.1)))
于 2012-11-20T21:48:43.073 回答
4

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

如果您想在每次迭代时复制列表的状态,有几个选项(请参阅如何在 python 中克隆列表?)。

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

这是浅拷贝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)))
于 2012-11-20T21:47:14.847 回答
1

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

于 2012-11-20T21:47:43.867 回答