技术周刊之解析Python中的赋值、浅拷贝、深拷贝

事情的起因

  • 本周我们分享的主题是Python中关于浅拷贝和深拷贝的特性,想要深入研究Python中的浅拷贝和深拷贝的起因在于,我想生成一个json字符串,该字符串未dumps之前是一个Python的数据结构,里面包含字典,以及List,在遍历生成dictionary时候,出现一个bug,就是每次遍历生成的dictionary都是上一次的值,现象可以看以下代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 这里我们定义一个函数get_data()
    def get_data():
    ...: appid_dict = {}
    ...: appid_all_dict = {}
    ...: import pdb;pdb.set_trace()
    ...: for i in range(10):
    ...: appid_dict['a'] = i
    ...: appid_all_dict[i] = appid_dict
    # 我们的初衷是想要得到
    # {0: {'a': 0}, 1: {'a': 1}, 2: {'a': 2}, 3: {'a': 3}}....这样的一个dict

    # 但是在调试过程中,发现得到的结果是这样的:
    # (Pdb) appid_all_dict
    # {0: {'a': 2}, 1: {'a': 2}, 2: {'a': 2}}
    # (Pdb)
    # 即,后面的appid_dict都会把前面的覆盖掉,这是什么原因呢?
    # 我们这里先把原因说一下:因为Python中对dict的操作默认是浅拷贝,即同样的字典,使用多次的话,每次使用都是指向同一片内存地址(引用),所以在上面的程序中后面对appid_dict的赋值,都将前面的给覆盖掉了,导致每一个appid_dict指向同一片内存,读取的当然就是最后一次的appid_dict的值,即上面程序的执行结果:
    {0: {'a': 9}, 1: {'a': 9}, 2: {'a': 9}, 3: {'a': 9}, 4: {'a': 9}, 5: {'a': 9}, 6: {'a': 9}, 7: {'a': 9}, 8: {'a': 9}, 9: {'a': 9}}
    • 那么如何修改这个bug,让程序输出我们想要得到的结果:

      1
      {0: {'a': 0}, 1: {'a': 1}, 2: {'a': 2}, 3: {'a': 3}, 4: {'a': 4}, 5: {'a': 5}, 6: {'a': 6}, 7: {'a': 7}, 8: {'a': 8}, 9: {'a': 9}}
    • 看完下面对于Python赋值、浅拷贝、深拷贝的解析,相信你就可以自己解决这个问题了

    Python中的赋值操作

    • 赋值:就是对象的引用
    • 举例: a = b: 赋值引用,a和b都指向同一个对象,如图所示

    Python中浅拷贝

    • a = b.copy(): a 是b的浅拷贝,a和b是一个独立的对象,但是它们的子对象还是指向同一片引用。
    • Python中对字典的默认赋值操作就是浅拷贝,所以导致了文章开头所出现的情况。

    Python中的深拷贝

    • 首先import copy,导入copy模块(Python中自带),b = copy.deepcopy(a), 我们就说b是a的深拷贝,b拷贝了a所有的资源对象,并新开辟了一块地址空间,两者互不干涉。

    实际的例子来进一步说明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    In [13]: import copy

    In [14]: def temp():
    ...: a = [1, 2, 3, 4, ['a', 'b']]
    ...: b = a # 赋值操作,直接传所有对象的引用
    ...: c = copy.copy(a) # 浅拷贝,子对象指向同一引用
    ...: d = copy.deepcopy(a) # 深拷贝,互不干涉
    ...: a.append(5) # 修改对象a
    ...: a[4].append('c') # 修改a中的数组
    ...: print( 'a = ', a )
    ...: print( 'b = ', b )
    ...: print( 'c = ', c )
    ...: print( 'd = ', d )
    ...:

    In [15]:

    In [15]: temp()
    a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
    b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
    c = [1, 2, 3, 4, ['a', 'b', 'c']]
    d = [1, 2, 3, 4, ['a', 'b']]

解决最初的问题

  • 看到这里,我们再回头看文章最初的那个问题,就可以很easy地解决了。

    1
    2
    3
    4
    5
    6
    7
    8
    def get_data():
    ...: appid_dict = {}
    ...: appid_all_dict = {}
    ...: import pdb;pdb.set_trace()
    ...: for i in range(10):
    appid_dict = copy.deepcopy(appid_dict)# 只需要加上这一行,使其成为深拷贝,问题解决!
    ...: appid_dict['a'] = i
    ...: appid_all_dict[i] = appid_dict

总结

要对Python的dictionary进行迭代分析,一定要注意其中的深拷贝问题,出现问题后,也要多往这方面考虑。

本期技术周刊到此结束。


-------------The End-------------

本文标题:技术周刊之解析Python中的赋值、浅拷贝、深拷贝

文章作者:cloud sjhan

发布时间:2018年09月09日 - 14:09

最后更新:2018年09月09日 - 16:09

原始链接:https://cloudsjhan.github.io/2018/09/09/技术周刊之解析Python中的赋值、浅拷贝、深拷贝/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

cloud sjhan wechat
subscribe to my blog by scanning my public wechat account
坚持原创技术分享,您的支持将鼓励我继续创作!
0%
;