您的位置:首页 > 编程语言 > Python开发

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

2013-05-03 23:04 721 查看
吃晚饭时跟同事讨论问题,发现关于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 ===============
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: