2012-11-20 118 views
0

python相当新,python类很新。问题有一点涉及。最欣赏你的耐心:python覆盖我的列表元素

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

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刚刚更新的“明星”的所有元素,然后返回“明星”:

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

现在,我正在努力推动这个事情,“明星”的各种更新存储在一个列表所谓的“历史”:

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被“重新指向”一个全新的存储位置,而一个[0]继续指向旧的b。第二,b中的内存被“编辑”,一个[0]仍然指向那个位置。追加后,a [1]也指向该位置。我现在有吗?

我对python很陌生,现在还在搞清楚pythonic哲学。但对我而言,重新分配直接完成的指针,但以更复杂的方式完成的“深层复制”与我通常想要做的事情相反。任何人都可以启发我吗?再次感谢。

+0

为什么'numstars'和'stars'类变量而不是实例变量? (并且,如果你确实有充分的理由,为什么要立即用'__init__'函数中的实例'numstars'替换类'numstars') – abarnert

+0

没有在'Star'中看到更新函数的代码,它是我很难推荐除[[copy.deepcopy]](http://docs.python.org/2/library/copy.html#copy.deepcopy) – inspectorG4dget

+0

以外的任何其他内容,在最后一行中出现语法错误的代码。 – MikeHunter

回答

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.velstepself.xstep中所做的操作。如果您修改了Star对象,那么简单(浅表)副本对您不会有任何好处(例如gal.time_stepper(0.1)[:])。 (你会创建一个新的列表,但它会保持不断更新的相同星星)。在这种情况下,当追加到历史记录列表中时,您需要copy.deepcopy

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

这是很好的...但它并没有真正解决OP的问题。他仍然需要执行'history.append(gal.time_stepper(.1))[:]',或者一些获取不同对象的等价方法。 – abarnert

+0

@abarnert - 是的,我意识到,但是我甚至不认为制作浅拷贝是可以的。我猜,但我敢打赌,OP需要一个'copy.deepcopy',因为我的钱说'Star'对象被修改了。 – mgilson

+0

你可能想通过一个拼写检查器来运行这个工具......即使从一个快速列表中,“galexy”和“mality”都会跳出来。但否则,一切都很好。 – abarnert

4

这是因为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))) 
+0

为什么使用'copy.copy'而不是'[:]'? – abarnert

+1

@abarnert可读性。 '[:]'属于'整洁的蟒蛇'类别,属于我。 –

+1

这是一个标准习惯用法,它在教程部分和官方常见问题解答中都有使用,讨论这个问题,所以避免它是不合理的。 – abarnert

1

它并没有改变或者重写所有的值,而是历史中的所有元素指向相同的数组对象。您需要按值分配,而不是通过使用数组的副本进行分配。