在一个类的实例中修改字典会对所有其他实例进行相同的更改

时间:2017-11-26 19:14:39

标签: python

我在python中发现了这个奇怪的故障。我正在玩进化模拟,所以我制作了大量的生物,每个生物都有它自己的基因。你可以想象,能够变异基因并用它创造新生物是很重要的。我将每个生物基因保存在字典中,每个基因都是字典中的一个列表(其中可能包含更多列表)。

当试图对基因应用突变时出现了问题。如果我没有复制基因字典,对字典的任何更改都会导致所有其他生物发生类似的变化。 (这不是一个大问题,但我有兴趣听到这个原因。字典和课程不能很好地合作吗?)

那么问题是什么?只是复制基因吧?好吧,我发现的是这个问题也适用于字典里面的列表。如果我不希望更改跨所有实例进行转换,我将不得不复制列表(copyList = List [:])。即使我正在对原始f * cking基因字典的COPY进行更改,也会发生这种情况。更重要的是,列表中的任何列表都必须得到相同的处理。更重要的是,如果我在课堂外调用类中的函数,我仍然会遇到这个问题。

这很烦人,但我认为可能还有其他我不知道的问题:其他情况的基因可能被破坏的其他方式。因此我一直在模拟我的模拟。复制后复制后复制会导致代码混乱。

有人知道为什么会这样吗?有没有一种简单的方法来解决它?下面是一些简化的代码来说明问题。我无法想象发生了什么。

tl; dr:运行下面的代码,看看一些奇怪的巫毒狗屎:

#DEMONSTRATION OF A F*CKING STUPID PHENOMENON WITH PYTHON CLASSES

class creature(object):
    def __init__(self,genes):
        self.genes = genes

    def changeGenes(self,details):
        genes = self.genes.copy()
        n,i,s = details
        try:
            genes[n][i] = s 
            self.genes = genes
            #WAY THAT WORKS
            #g = genes[n][:]
            #g[i] = s
            #genes[n] = g
            #self.genes = genes
        except:
            print('error: incorrect key or index')

def main():
    active_objects = []
    genes = {'1': [1,2,3],
             '2': [1,2,3]}
    for n in range(3):
        active_objects.append(creature(genes))

    while True:
        print('\nPlease choose what to do: ')
        print('1) Change genes of first creature')
        print('2) Print all genes')
        c = input('Your choice: ')

        if c == '1':  
            n = input('Choose gene key: ')
            i = int(input('Choose gene index: '))
            s = input('Choose symbol to replace gene: ')
            active_objects[0].changeGenes([n,i,s])
        elif c == '2':
            for item in active_objects:
                print(item.genes)
        elif c == '0':
            print('Thank you! Come again!')
            break
        else:
            print('error: invalid input')

main()

2 个答案:

答案 0 :(得分:1)

这不是故障,而是预期的行为。如果将字典传递给新变量,则字典是引用,新变量仍将引用旧字典(如果要在嵌套字典中深入更改某些值,则此行为实际上非常有用)。如果你想要一个类似于旧字典的新字典,你必须做一个深度复制。 .copy创建一个浅层副本,其中嵌套字典仍将被视为引用。

试试吧:

from copy import deepcopy

class creature(object):
    def __init__(self, genes):
        self.genes = deepcopy(genes)

它会起作用。

就此而言,将可变引用传递给函数也不是一个好主意。只是因为如果从其他地方引用它们,它们可以随时改变。您应该研究不可变数据结构。收集模块有一些非常有趣的选项。

答案 1 :(得分:1)

正如您所注意到的,当两个变量指向可变对象的同一个实例(在您的情况下,是一个字典或列表)时,所有指向它的变量都会看到对该实例的修改。

"变量"也可以是一个dict元素,或列表中的位置...... python中的赋值不会复制对象(这会很慢)并使用引用(即指针)代替。因此,相同的对象实例可以在多个位置具有指向它的引用。这通常是可取的:

a = MyClass()
doSomethingTo( a )

如果doSomethngTo()实际上对它做了什么,你会期望修改。发生这种情况是因为对对象a的引用作为参数传递给函数,而不是对象的副本。

一种解决方案是使用copy.deepcopy模块。

您还可以使用元组替换列表,并创建新元组而不是修改列表。但是你仍然需要复制字典。