Python 的可变性
我自己 fork 了一份 iOSFakeRun 项目,并放到了我的私有仓库里。同时,我添加了随机生成路径的代码。可是,我发现代码最后生成的随机路径中,不乏有重复的路径。有一定经验的人,很容易看出这就是 Python 里的 mutability 问题。Python 的可变性其实是一个挺恶心的一点:int 等式不变的,但是 list 等又是可变的。与 C/C++ 等对可变性控制非常严格的语言不同,新手很容易陷入 mutability 中。
我的问题:
首先,上面的命令是一个浅拷贝。如果 coords 里面的 elements 是 immutable 的,那么,依靠上面的浅拷贝,就会导致 elements 重复。比如:
>>> a=[{"a":1,"b":2},{"a":5,"b":-3}]
>>> a*=2
>>> print(list(map(id,a)))
[2103007505344, 2103006655552, 2103007505344, 2103006655552]
不难看出,由于 dict 是可变对象,我们采用浅拷贝之后,就会有重复的元素(对于同类型元素,id 一样,元素就是“同一个”)。
如果采用深拷贝:
>>> from copy import deepcopy
>>> a=[{"a":1,"b":2},{"a":5,"b":-3}]
>>> new_a=[]
>>> for _ in range(2):
... new_a+=deepcopy(a)
...
>>> a=new_a
>>> print(list(map(id,a)))
[2103009995264, 2103010101760, 2103010105280, 2103010109888]
可以看到,就都不一样了。
对于原问题,我们可以采用同样的方法:
from copy import deepcopy
# ...
new_coords = []
for _ in range(repeat_time):
new_coords += coords
coords = new_coords
原理
本质上,deepcopy 就是 recursive shallow_copy。举例:
>>> a=[{"a":1,"b":2},{"a":5,"b":-3}]
>>> new_a=[]
>>> for _ in range(2):
... for i in a:
... new_a.append(i.copy())
...
>>> a=new_a
>>> print(list(map(id,a)))
[1588961447424, 1588961447168, 1588961454336, 1588961447360]
我们这里由于已经提前知道了要处理的数据类型,因此不需要递归实现,而只需要实现一个 domain-specific algorithm 即可。
至于完整版本,deepcopy 的具体实现为:
from copy import copy
def deepcopy(obj):
if isinstance(obj, dict):
new_obj = {}
for key, value in obj.items():
new_obj[key] = deepcopy(value)
return new_obj
elif isinstance(obj, list):
new_obj = []
for item in obj:
new_obj.append(deepcopy(item))
return new_obj
elif isinstance(obj, set):
new_obj = set()
for item in obj:
new_obj.add(deepcopy(item))
return new_obj
elif isinstance(obj, tuple):
new_obj = ()
for item in obj:
new_obj += (deepcopy(item),)
return new_obj
else:
return copy(obj)
而 copy 的实现为:
def copy(obj):
if isinstance(obj, dict):
new_obj = {}
for key, value in obj.items():
new_obj[key] = value
return new_obj
elif isinstance(obj, list):
new_obj = []
for item in obj:
new_obj.append(item)
return new_obj
elif isinstance(obj, set):
new_obj = set()
for item in obj:
new_obj.add(item)
return new_obj
elif isinstance(obj, tuple):
new_obj = ()
for item in obj:
new_obj += (item,)
return new_obj
else:
return obj