zoukankan      html  css  js  c++  java
  • [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

  • 相关阅读:
    2020软件工程作业00——问题清单
    2020软件工程作业03
    2020软件工程作业02
    2020软件工程作业01
    2020软件工程个人作业06——软件工程实践总结作业
    2020软件工程作业00
    2020软件工程作业03
    2020软件工程作业02
    2020软件工程作业01
    2020软件工程个人作业06——软件工程实践总结作业
  • 原文地址:https://www.cnblogs.com/wildkid1024/p/11093820.html
Copyright © 2011-2022 走看看