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

Python 在类的一个实例中修改字典会对所有其他实例进行相同的更改,python,Python,我在python中发现了这个wierd小故障。我在玩进化模拟,所以我制作了很多生物,每个生物都有自己的基因。正如你所能想象的,重要的是能够变异基因并用它创造新的生物。我把每个生物的基因保存在字典里,每个基因都是字典里的一个列表(字典里可能有更多的列表) 当试图对基因进行突变时,问题就出现了。如果我不复制基因词典,对词典的任何修改都会导致所有其他生物发生类似的变化。(这不是什么大问题,但我很想听听原因。字典和课程配合得不好吗?) 那有什么问题?只是复制基因,对吗?我发现这个问题也适用于字典里的列表

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

当试图对基因进行突变时,问题就出现了。如果我不复制基因词典,对词典的任何修改都会导致所有其他生物发生类似的变化。(这不是什么大问题,但我很想听听原因。字典和课程配合得不好吗?)

那有什么问题?只是复制基因,对吗?我发现这个问题也适用于字典里的列表。如果我不想在所有实例之间转换更改,我必须复制列表(copyList=list[:])。即使我在修改原始的f*CKK基因词典的副本,这种情况也会发生。更重要的是,列表中的任何列表都必须得到相同的处理。更重要的是,如果我在我调用的类之外的函数中进行更改,我仍然会遇到这个问题

这很烦人,但我想可能还有其他我不知道的问题:其他情况下的基因可能以其他方式被破坏。由于这个原因,我的模拟一直存在问题。一次又一次的复制会导致代码混乱

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

tl;博士:运行下面的代码来查看一些巫毒狗屎:

#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()

这不是小故障,而是有意的行为。字典是引用如果将它们传递给新变量,新变量仍将引用旧字典(如果要更改嵌套字典中的某些深层值,此行为实际上非常方便)。如果你想有一本与旧词典相似的新词典,你必须做一本深度复制。copy创建一个浅拷贝,其中嵌套字典仍将被视为引用

试试看:

from copy import deepcopy

class creature(object):
    def __init__(self, genes):
        self.genes = deepcopy(genes)
它会起作用的


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

这不是小故障,而是预期的行为。字典是引用如果将它们传递给新变量,新变量仍将引用旧字典(如果要更改嵌套字典中的某些深层值,此行为实际上非常方便)。如果你想有一本与旧词典相似的新词典,你必须做一本深度复制。copy创建一个浅拷贝,其中嵌套字典仍将被视为引用

试试看:

from copy import deepcopy

class creature(object):
    def __init__(self, genes):
        self.genes = deepcopy(genes)
它会起作用的


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

正如您所注意到的,当两个变量指向可变对象的同一实例(在您的例子中是dict或列表)时,所有指向该实例的变量都会看到对该实例的修改

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

a = MyClass()
doSomethingTo( a )
如果doSomethngTo()确实对a做了一些修改,您可能会希望a被修改。这是因为对对象a的引用作为参数传递给函数,而不是对象的副本

一种解决方案是使用该模块


您还可以用元组替换列表,并创建一个新元组,而不是修改列表。不过,您仍然需要复制dict。

正如您所注意到的,当两个变量指向可变对象的同一实例(在您的例子中,是dict或列表)时,所有指向该实例的变量都会看到对该实例的修改

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

a = MyClass()
doSomethingTo( a )
如果doSomethngTo()确实对a做了一些修改,您可能会希望a被修改。这是因为对对象a的引用作为参数传递给函数,而不是对象的副本

一种解决方案是使用该模块

您还可以用元组替换列表,并创建一个新元组,而不是修改列表。不过,你还是需要抄写口述