Python - 一个变量在不该相等时等于另一个变量

6 投票
6 回答
24633 浏览
提问于 2025-04-11 09:35

这是我的示例代码。它是一个用于高斯-赛德尔方法的迭代过程(矩阵求解器)。简单来说,当误差足够小的时候,它会跳出循环。

i=1
while (i>0):
    x_past = x_present

    j=0
    while(j<3):
        value=0
        k=0
        while(k<3):
            if(k!=j):
                if(i==1):
                    if(k>j):
                        value=value+0
                    else:
                        value=value+x_present[k]*eqn[j][k]    
                else:
                    value=value+x_present[k]*eqn[j][k]
            else:
                value=value+eqn[j][k]
            k=k+1
        x_present[j:j+1]=[value]
        j=j+1
    print "X_PAST"
    print x_past
    print "X_PRESENT"
    print x_present    
    if(error(x_past, x_present)<10**-2):
        break;
    i=i+1

我把代码简化了一下,这样更容易管理。如果你不明白它在做什么,其实对解决这个问题来说并不是特别重要。

现在说说我遇到的问题。每次运行

x_present[j:j+1]=[value]

时,x_past 都会被设置为 x_present。我不知道为什么会这样,因为我只在循环的开头把 x_past 设置为 x_present。如果我去掉

x_past=x_present

这一句,x_past 就不会再等于 x_present。这让我觉得可能是这两句代码的某种组合导致了这个问题。

这真是个大问题,因为如果 x_past 等于 x_present,那么每次的误差就会等于 0,循环在第一次迭代后就会结束。代码确实能工作,比如如果我让代码运行 8 次迭代,然后再跳出循环,它会给我正确的答案。

我已经试着解决这个问题 4 个小时了,完全搞不懂。我学 Python 的时间不长,所以在语法方面的排错能力也不是很好。任何帮助都非常感谢!!

6 个回答

3

正如其他人提到的,解决办法是把 x_past = x_present 改成 x_past = x_present[:]。一般来说,你可以使用一个叫 copy 的模块来复制Python中的对象。

>>> import copy
>>> a = range(10)
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> b = a
>>> a += 10, 11
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>> b
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>> c = copy.copy(a) # shallow copy
>>> c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>> del a[3:]
>>> a
[0, 1, 2]
>>> b
[0, 1, 2]
>>> c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

你的代码至少可以说是不够Python风格的。

可以用下面这样的代码来替代:

import copy
# assert(len(x_present) >= len(eqn))

first = True
while True:
    x_past = copy.copy(x_present) # copy

    for j, eqj in enumerate(eqn):
        x_present[j] = sum(x_present[k] * eqj[k] 
                           for k in range(j if first else len(eqj)) 
                           if k != j)
        x_present[j] += eqj[j] 

    print "X_PAST\n%s\nX_PRESENT\n%s" % (x_past, x_present)
    if allequal(x_past, x_present, tolerance=10**-2):
        break
    first = False

这里有一个 allequal() 的定义(使用绝对误差。根据你的情况,这可能是个好主意,也可能不是(你也可以使用相对误差)):

def allequal(x, y, tolerance):
    return (len(x) == len(y) and 
            all(-tolerance < (xx - yy) < tolerance
                for xx, yy in zip(x, y)))
4

x_past和x_present是什么?我对Python了解不多,但从.NET或Java的角度来看,如果它们是某种数据结构的引用(比如一个映射或其他),那么一开始把它们指向同一个对象,就意味着通过一个变量所做的任何更改,另一个变量也能看到。这听起来像是你需要复制这个数据结构,而不仅仅是做一个引用赋值。你正在使用的数据结构有没有什么“克隆”功能可以用?

不过如我所说,我对Python了解不多,所以这可能完全是错的……

31

是的,我觉得这里的回答能说明你的问题。让我稍微解释一下。

你在引用一个列表,所以当这个列表发生变化时,所有引用这个列表的地方都会看到这个变化。为了演示这一点:

>>> x_present = [4,5,6]
>>>
>>> x_past = x_present
>>>
>>> x_past
[4, 5, 6]
>>>
>>> x_present.append(7)
>>>
>>> x_past
[4, 5, 6, 7]
>>>

如果你想要一个列表的副本,你需要这样做,listcopy = mylist[:]。(或者可以用 import copy;listcopy = copy.copy(mylist)

>>> x_past = x_present[:]
>>> x_past
[4, 5, 6, 7]
>>>
>>> x_present.append(8)
>>>
>>> x_past
[4, 5, 6, 7]

撰写回答