[python][进阶之路]list复制引发的问题
问题来源
当我使用py时,总是随手使用list/dict保存一切(尽管仍然有更效率的数据数据结构),因为list/dict足够方便,而且包含了大部分的常用操作。可是最近我发现了一些问题,这些问题可能会导致严重的错误。
一个简单的例子
复制语句是比较常用的语句,但是如果在py中不小心使用了list的复制,那么下面的代码会带来灾难:
a = [0, 1, 2, 3]
b = a
for item in a:
b.append(item)
print(b)
如果按照正常的理解,那么b应该是a的双份数据,也就是 b = [0, 1, 2, 3, 0, 1, 2, 3],但是如果你真的运行以上的代码,就会发现CPU会很快上升到100%,为什么会出现这样呢?是不是py的设计缺陷呢?
我们先完成上面的需求,就是把a进行double copy成b,那么正确的写法应该是:
a = [0, 1, 2, 3]
b = a * 2
print(b)
实际上上述代码使用了生成器,对list/dict进行乘法运算时,往往是将其扩展为n倍。注意这里要和numpy中的操作加以区别。
一切皆对象
在Python Reference[1]中关于Data Model的说明中提到,对象是python对数据的抽象,在python的世界里,一切皆对象。其中最基本的数值,字符串,甚至函数都是一个对象,在py中自定义对象时,可以设置callable方法,可以使得对象像函数一样被调用。
每个对象均包含3个基本元素:identify,type,value。其中,对象的id是创建对象时分配的,在其生存期内保持不变,id是内存地址的一个散列值;对象的type也是对象创建时就决定了的,在生命周期内不可变;而value既可能为可变的,又可能为不可修改的,具体情况视其type而定。
Python的built-in function中提供了id()来返回对象的identify value,提供了type()来返回对象的type。
根据Python Reference关于Naming and binding[2]的说明,对象的name是该对象的引用,在这里,“引用”的背后的含义是指对象的name只是对象的一个tag而已,也即是说,python的复制语句实际上并不是真正的复制语句,而是类似于c语言中的指针,指向的是对象的内存地址。
如以下代码所示:
a = 1
b = a
print(id(a),id(b))
上面的代码中id(a)和id(b)将指向同一个值,也即是两者指向了同一个内存地址,当改变b的值的时候,a同时也会发生改变。
py的‘指针’
在c语言中最让人头疼的一个特性就是指针,指针是为了解决c语言的一些缺陷而设计的,本身非常灵活,于是cpp也继承了c的指针特性,但是并不是所有人都能够用好指针,特别是对于新手,使用指针往往会导致令人崩溃的错误。py在设计时吸取了教训,抛弃了指针特性,但是在写py程序的时候,我仍然能够感觉到ptr in everywhere。
在复制语句的时候,py的表现让人感觉怪异,那么当查看renfrence的复制语句的时候,我们可以发现这么一句话:
Assignment statements are used to (re)bind names to values and to modify attributes or items of mutable objects.
也就是复制语句实际上是一种绑定,是声明了一个指向某个对象的内存地址的‘指针’。那么以下代码便很容易理解了:
a = 1 # int *a = Int(1)
b = a # int *b = a
How to do
那么该如何做来避免上述的问题呢, 有3种常用的方法:
-
引入copy库,注意这里是浅copy
import copy a = [0, 1, 2, 3] b = copy.copy(a) for item in a: b.append(item) print (a) print (b)
-
既然一切皆对象,那么使用对象方法重新生成一个:
a = [0, 1, 2, 3] b = list(a) for item in a: b.append(item) print (a) print (b)
-
切片法,切片会生成一个原对象的一个copy:
a = [0, 1, 2, 3] b = a[:] for item in a: b.append(item) print (a) print (b)
引用
[1]. https://docs.python.org/3.6/reference/datamodel.html#objects-values-and-types
[2]. https://docs.python.org/3.6/reference/executionmodel.html#naming-and-binding