最近在备课一个AI的俄罗斯方块项目,在模拟填充加权计算环节,遇到了一个列表变量赋值、浅拷贝、深拷贝的问题,方便理解,以下对场景做简化。

class A:
   def __init__(self, data = None):
       if data == None:
           data = []
       self.data = data

   def add(self, var):
       self.data.append(var)

obj1 = A([1, 2, 3])
print(obj1.data)  # 输出:[1, 2, 3]

obj2 = A(obj1.data)
obj2.add(4)  # 对obj2.data添加元素
print(obj2.data)  # 输出 [1, 2, 3, 4]
print(obj1.data)  # 输出 [1, 2, 3, 4]

以上是原始场景,bug出现在最后一行,当对obj2添加元素后,obj1.data 也会跟着变化,但初始目的是只传参,不想改变 obj.data 的原始值。

问题出现的原因是 obj2 = A(obj1.data) 实例化obj2时,其实是将 obj1.data 赋值给__init__的参数data,相当于obj2.data = obj1.data。

这样就出现了变量赋值引用,两个值其实是指向同一个地址的问题

class A:
   def __init__(self, data = None):
       if data == None:
           data = []
       self.data = data

   def add(self, var):
       self.data.append(var)

obj1 = A([1, 2, 3])
print(obj1.data)  # 输出:[1, 2, 3]

obj2 = A(obj1.data.copy())  # 或者 obj2 = A(list(obj1.data))
obj2.add(4)  # 对obj2.data添加元素
print(obj2.data)  # 输出 [1, 2, 3, 4]
print(obj1.data)  # 输出 [1, 2, 3, 4]

以上是改进方案,用浅拷贝的方式,使其指向不同地址,看上去像解决了问题。但之后我又遇到了,一个二维列表修改元素的问题。

class A:
   def __init__(self, data=None):
       if data == None:
           data = []
       self.data = data

   def setVal(self, x, y, var):
       self.data[x][y] = var

obj1 = A([[0] * 3 for _ in range(3)])
print(obj1.data) #输出 [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

obj2 = A(obj1.data)
obj2.setVal(1, 1, 1)
print(obj2.data) #输出 [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
print(obj1.data) #输出 [[0, 0, 0], [0, 1, 0], [0, 0, 0]]

可以看到,在对 obj2.setVal() 之后,对 obj1.data 也会产生影响,所以浅拷贝只复制父对象就不够了,得用深拷贝才能解决,代码如下。

import copy
class A:
   def __init__(self, data=None):
       if data == None:
           data = []
       self.data = data

   def setVal(self, x, y, var):
       self.data[x][y] = var

obj1 = A([[0] * 3 for _ in range(3)])
print(obj1.data) #输出 [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

obj2 = A(copy.deepcopy(obj1.data)) #深拷贝
obj2.setVal(1, 1, 1)
print(obj2.data) #输出 [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
print(obj1.data) #输出 [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

Python这个语言挺有意思,一个拷贝的事整这么花哨,得处处小心。

解决方案参考:https://www.runoob.com/w3cnote/python-understanding-dict-copy-shallow-or-deep.html

本文为 陈华 原创,欢迎转载,但请注明出处:http://ichenhua.cn/read/195