“回👋掏”。最近做东西,有点儿玩不转复杂数据类型,写篇博文再回顾下深、浅拷贝相关知识。深、浅的区分主要在对复杂数据类型进行操作的时候。
By the way:时间过得很快,十月了,之前定了个小目标:一个月至少一篇文章产出。2020年的 $ \frac{5}{6} $ 已经过去。很庆幸自己坚持了下来,学到了不少东西。实习期间其实有不少的文章主题的想法,但真正想动手写篇博文的时候,发现事情并没有想想中的那么简单,一个主题涉及到的知识点还是蛮多的,再加上实践经验的不足,有些东西很难写道点上,copy & paste 总是不太好的『努力提高文章质量,hhh~』。希望自己后续继续加油。 
 
 
浅拷贝(shallow copy) 
浅拷贝总结:新对象内容为原对象内第一层对象的引用 。 
 
 
Python 中的浅拷贝 
关键点就在于这第一层对象。让我们先看看 Python 中的浅拷贝。
先看看不含嵌套元素的情形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 l1 = [1 , 2 , 3 ] l2 = l1 print (l1 is  l2)  l3 = list (l1) print (l1 is  l3)  l4 = l1[:] print (l1 is  l4)  print (id (l1), id (l2), id (l3), id (l4))  
 
含嵌套元素的情形:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 l1 = [1 , [2 ,3 ], 4 ] l2 = l1 l3 = list (l1) l4 = l1[:] for  first, second, third, fourth in  zip (l1, l2, l3, l4):         print ("value" , first, "address:" , id (first), id (second), id (third), id (fourth)) l4[1 ].append("new" ) print (l1)  print (l2)  print (l3)  print (l4)  for  first, second, third, fourth in  zip (l1, l2, l3, l4):         print ("value" , first, "address:" , id (first), id (second), id (third), id (fourth)) 
 
从上面的示例可以看到,Python中切片操作、工厂函数和=操作均是浅拷贝,只拷贝了原对象的第一层对象的引用,对第一层对象的操作会影响到其它对元对象进行浅拷贝的对象。但=操作和切片、构造器(工厂函数)不同的是,=操作不会创建新的对象。
值得注意的是,Python 中 tuple 的 tuple() 和切片操作和=进行的拷贝一样,不会创建新的对象。字典的浅拷贝可以使用 dict.copy()。
JS 中的浅拷贝 
让我们再来看看 JS 中的浅拷贝操作。
老规矩,先看看简单对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 let  obj1 = {  a : 1 ,   b : 2  }; let  obj2 = obj1;  let  obj3 = Object .assign ({}, obj1);  console .log (obj3)let  obj4 = {...obj1};  obj2.a  = "new" ; console .log (obj1, obj2, obj3, obj4)
 
再看下复杂对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 let  obj1 = {  a : {     b : 1 ,     c : 2    },   d : 3  }; let  obj2 = obj1;  let  obj3 = Object .assign ({}, obj1);  let  obj4 = {...obj1};  obj2.a .b  = "new" ; console .log (obj1);  console .log (obj2);  console .log (obj3);  console .log (obj4);  
 
可以看到,JS 对象的=操作、Object.assign({}, originObject) 和对象扩展运算均是浅拷贝。但是 Object.assign和对象的扩展运算对只有一层的对象进行的是深拷贝。此外 JS 数组「array 也是 object」的 map、reduce、filter、slice 等方法对嵌套数组进行的也是浅拷贝操作。
可以明显的看到,JS 和 Python 中的浅拷贝拷贝的均是第一层对象的引用。 
深拷贝(deep copy) 
深拷贝总结:创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。 
 
 
Python 中的深拷贝 
在 Python 中实现复杂对象的拷贝可以通过标准库copy  提供的 copy.deepcopy 实现,此外 copy 模块还提供了 copy.copy 进行对象的浅拷贝。
看下深拷贝的情况:
1 2 3 4 5 6 7 8 import  copyl1 = [1 , [2 , 3 ], 4 ] l2 = copy.deepcopy(l1) l2[1 ].append("new" ) print (l1)  print (l2)  
 
可以看到,有别于浅拷贝,对深拷贝 l1 的新对象 l2 的子元素增加新元素,并不会影响到 l1。
JS 中的深拷贝 
在 JS 中进行复杂对象的深拷贝,可以使用 JSON.stringify 先将 JS 对象转成 JSON 再转 JS 对象,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let  obj1 = {  a : {     b : 1 ,     c : 2    },   d : 3  }; let  obj2 = JSON .parse (JSON .stringify (obj1));obj2.a .b  = "new" ; console .log (obj1);  console .log (obj2); 
 
可以看到,深拷贝后对新对象深层次对象的更改不会使原对象发生变更。
手动实现深拷贝操作 
在某些情况下需要我们实现深拷贝操作,比如对自定义数据类型进行深拷贝。前面 JS 所述使用 JSON 进行的深拷贝方法仍有缺陷,比如:会忽略 undefined、会忽略 symbol、不能序列化函数、不能解决循环引用的对象。这时候就需要了解波深拷贝的实现了。
从前面所述可知,深拷贝与浅拷贝的区别主要在于 copy 的层次,浅拷贝 copy 的是第一层对象的引用,深拷贝需要 copy 深层次对象。So,以 deepcopy 层次 Object 为例子,要实现真正的深拷贝操作则需要通过遍历键来赋值对应的值,这个过程中如果遇到 Object 类型还需要再次进行遍历「同样的方法」。递归无疑了。来看波实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function  deepclone (obj ) {  let  map = new  WeakMap ();    function  deep (data ) {     let  result = {};          const  keys = [...Object .getOwnPropertyNames (data), ...Object .getOwnPropertySymbols (data)]     if  (!keys.length ) return  data;     const  exist = map.get (data);     if  (exist) return  exist;     map.set (data, result);     keys.forEach (key  =>  {       let  item = data[key];       if  (typeof  item === 'object'  && item) {         result[key] = deep (item);       } else  {         result[key] = item;       }     })     return  result;   }   return  deep (obj); } 
 
OK,再看些 Python 的 copy.deepcopy 的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def  deepcopy (x, memo=None , _nil=[] ):    """Deep copy operation on arbitrary Python objects.    See the module's __doc__ string for more info.   """     if  memo is  None :         memo = {}     d = id (x)    y = memo.get(d, _nil)    if  y is  not  _nil:       return  y          ... 
 
emm…,实现思想也是使用递归,同时借助了 memo (备忘录)解决对象的循环引用问题,避免 StackOverflow。
参考