[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种常用的方法:

  1. 引入copy库,注意这里是浅copy

    import copy
    a = [0, 1, 2, 3]
    b = copy.copy(a)
    for item in a:
       b.append(item)
    print (a)
    print (b)
    
  2. 既然一切皆对象,那么使用对象方法重新生成一个:

    a = [0, 1, 2, 3]
    b = list(a)
    for item in a:
       b.append(item)
    print (a)
    print (b)
    
  3. 切片法,切片会生成一个原对象的一个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

[3]. http://henry.precheur.org/python/copy_list

posted @ 2019-06-26 20:49  wildkid1024  阅读(679)  评论(0编辑  收藏  举报