【Python学习笔记】list/dict对象复制的误区与正确方法

       吃晚饭时跟同事讨论问题,发现关于python的一个有趣问题,起源如下:
       同事Y:python的list用法有个坑。。。
       我:啥坑? 
       同事Y:下面的用法会出现死循环(同事Y口头说的,可翻译成下面的代码)

a = [0, 1, 2, 3]
b = a
for item in a:
    b.append(item)
        我:啊。。。 之前没注意呀 我待会去试试。。。
        后来在机器上试验了一下,果然死循环了(某CPU占用100%),幸亏现在的机器都是多核,还不至于被搞到重启。。。
        查阅资料后,终于搞清楚了原因,作为笔记,记录于此。
        要理解上面那段导致死循环的代码的背后原因,需要先理解python中的对象、变量名及赋值符(=)这些基础概念,下面开始正文。

1. Object and Naming in Python
        在Python Reference中关于Data Model的说明中提到,对象是python对数据的抽象,在python的世界里,万物皆对象。每个对象均包含3个基本元素:identify,type,value。其中,对象的id是创建对象时分配的,在其生存期内保持不变,可以把id值看做是对象在内存中的地址;对象的type也是对象创建时就决定了的,在生命周期内不可变;而value既可能为可变的,又可能为不可修改的,具体情况视其type而定。
        Python的built-in functions中提供了id()来返回对象的identify value,提供了type()来返回对象的type。
        根据Python Reference关于Naming and binding的说明,对象的name是该对象的引用,在这里,“引用”的背后的含义是指对象的name只是对象的一个tag而已,更进一步讲,可能会有不同的对象名(多个tag)引用到同一个对象。文字不好描述,可以借助下面的代码来理解:

   a = 1
   b = a
        在这里,第1行创建了一个int对象,且用a来引用;第2行则表示,b也绑定到a引用的对象上(而不是创建b对象并用a引用的对象来初始化,这个行为与C语言不同,这也是导致本文开始给出那段代码死循环的原因)。
        可能有人会问:有什么证据来说明a和b均引用同一对象?
        答案是:用id()来验证,很容易看到id(a)跟id(b)返回的值是一样的,这就表明了它们引用的是同一个内存对象。 

2. Assignment in Python
        其实从上面给出的示例代码中,我们已经可以看到Python中赋值语句的“怪异行为”。为理清概念,我们可以继续查阅Python Reference关于Assignment Statements的说明,关键的一句摘出如下:
         Assignment statements are used to (re)bind names to values and to modify attributes or items of mutable objects.
        也就是说,赋值语句只是将变量名与对象做绑定或重绑定而已,它并不创建新对象。
        可能又有人会问:a = 1; id(a); a = 2; id(a) 两次id()返回不同值,怎么解释?是不是说明这种情况下,赋值操作创建了新对象?
        答案是:不是的。真实情况是,1和2是两个不同的对象(可以用id(1); id(2)来验证),两次赋值将a绑定到不同的对象,返回的id不同也是符合预期的。而且我们执行a = 1; id(1); id(a)可以发现两次id()返回值相等,这就表明a确实只是绑定到对象1而已。
3. List/Dict对象复制的正确方法
        根据前面的基础概念,我们已经知道,执行a = [0, 1, 2]; b = a 后,b与a均引用同1个list,所以本文开始给出的那段代码会导致死循环也就很容易理解了。下面介绍3种list/dict对象复制的正确方法。

            3.1 copy / deepcopy
        参考python reference - copy ,具体用法如下:
    
    import copy
    a = [0, 1, 2, 3]
    b = copy.copy(a)
    id(a)
    id(b)
    for item in a:
       b.append(item)
    print a
    print b
           执行这段代码可知,a和b引用的是不同的对象,因此不会引起死循环。
        copy的行为类似于C++中的浅拷贝,deepcopy则类似于C++深拷贝,具体用法可以参考python documentation相关章节。  

          3.2 new_obj = list(old_obj)
       根据文档,Python的built-in函数list()创建1个与old_obj完全相同的新对象并返回,因此显然该方法可以达到对象正确复制。

    a = [0, 1, 2, 3]
    b = list(a)
    id(a)
    id(b)
    for item in a:
       b.append(item)
    print a
    print b
        3.3 new_obj = old_obj[:]
      [:]的方法其实是利用了python的slice特性,比较tricky,新手不易理解。虽可以解决问题,但个人不推荐使用。
   
    a = [0, 1, 2, 3]
    b = a[:]
    id(a)
    id(b)
    for item in a:
       b.append(item)
    print a
    print b
         通过上面的介绍,我们已经搞清了python中list对象复制的正确方法,其实不限于list,dict类型的对象也有同样的问题。
【参考资料】
1. 上文中给出的Python Reference Links
2.  Python: copying a list the right way 


================ EOF ===============


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中适用于`str`、`bytes`、`list`、`set`、`dict`等对象的内建函数有很多,下面列举一些常用的: 1. `len()`:返回对象的长度或元素个数。 2. `type()`:返回对象的类型。 3. `isinstance()`:判断对象是否为指定类型。 4. `id()`:返回对象的唯一标识符。 5. `max()`:返回对象中的最大值。 6. `min()`:返回对象中的最小值。 7. `sorted()`:返回一个排序后的列表或可迭代对象。 8. `reversed()`:返回一个反转后的列表或可迭代对象。 9. `enumerate()`:返回一个枚举对象,包含索引和元素。 10. `zip()`:将多个可迭代对象打包成元组,返回一个zip对象。 11. `map()`:将函数作用于可迭代对象的每个元素,返回一个map对象。 12. `filter()`:过滤可迭代对象中符合条件的元素,返回一个filter对象。 13. `sum()`:返回可迭代对象中所有元素的总和。 14. `any()`:判断可迭代对象中是否有任何一个元素为True。 15. `all()`:判断可迭代对象中是否所有元素都为True。 16. `chr()`:返回对应Unicode码的字符。 17. `ord()`:返回字符对应的Unicode码。 18. `str()`:将对象转换为字符串。 19. `bytes()`:将对象转换为字节串。 20. `list()`:将对象转换为列表。 21. `set()`:将对象转换为集合。 22. `dict()`:将对象转换为字典。 以上仅是常用的一部分,Python中内建函数非常丰富,具体可以参考官方文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值