拷贝和视图(numpy和list)

一.NumPy中的数组

当对数组进行运算和操作时,其数据有时会被拷贝到一个新的数组而有时又不会拷贝。这一点常常对刚使用NumPy的用户造成困惑。以下有三种情况:

1. 完全不拷贝

简单的任务是不会对数组或其数据进行拷贝的。

    >>> a = np.arange(12)
    >>> b = a            # 不会创建新的对象
    >>> b is a           # a和b是同一个ndarray对象的两个名字,对a的任何操作都会影响b(包括更改值以及更改shape),反之亦然
    True
    >>> id(b) == id(a)  # id()返回 Python 对象的通用标识符,类似于 C 中的指针
    True
    >>> a[1] = 34    # 也会改变b的值
    >>> b
    array([ 0, 34,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
    >>> b.shape = 3,4    # 也会改变a的形状
    >>> a.shape
    (3, 4)

Python传递可变对象的引用,因此函数调用不会进行拷贝。

    >>> def f(x):
    ...     print(id(x))
    ...
    >>> id(a)    # id是对象的唯一标识
    148293216
    >>> f(a)
    148293216

2. 视图或浅拷贝(使用view以及切片)

不同的数组对象可以共享相同的数据。view方法会创建一个共享原数组数据的新的数组对象。

如果数组A是数组B的视图(view),则称B为A的base(除非B也是视图)。视图数组中的数据实际上保存在base数组中。

    >>> c = a.view()  #完全等价于 c = a[:],这是切片,所以是相当于视图,并且是全部切片,所以值就等于a.view()
    >>> c is a
    False
    >>> id(c) == id(a)       # id()返回 Python 对象的通用标识符,类似于 C 中的指针
    False
    >>> c.base is a          # c is a view of the data owned by a
    True
    >>> a.flags.owndata      # a拥有数据
    True
    >>> c.flags.owndata      # c并不拥有数据
    False
    >>>
    >>> c.shape = 2,6        # a的形状并不随之改变(并没有更改数据,所以不会相互影响)
    >>> a.shape
    (3, 4)
    >>> c[0,4] = 1234        # a的数据也会变
    >>> a
    array([[   0,    1,    2,    3],
           [1234,    5,    6,    7],
           [   8,    9,   10,   11]])

对数组切片返回的是其视图:

    >>> s = a[ : , 1:3]     # spaces added for clarity; could also be written "s = a[:,1:3]"
    >>> s[:] = 10           # s[:]是s的视图。注意s=10 和 s[:]=10的不同
    >>> a
    array([[   0,   10,   10,    3],
           [1234,   10,   10,    7],
           [   8,   10,   10,   11]])

注:浅拷贝只有当更改数据时才会相互影响,而更改shape时(数据没变),所以不会相互影响。

3. 深拷贝(使用copy,相互不影响)

copy方法会对数组和其数据进行完全拷贝。


    >>> d = a.copy()      # 创建了新的数组和新的数据
    >>> d is a
    False
    >>> id(b) == id(a)    # id()返回 Python 对象的通用标识符,类似于 C 中的指针
    False
    >>> d.base is a       # d没有和a共享任何数据,a或者d任意一方的改变并不会影响对方
    False
    >>> d[0,0] = 9999
    >>> a
    array([[   0,   10,   10,    3],
           [1234,   10,   10,    7],
           [   8,   10,   10,   11]])

二.标准python中的list(列表)

以上是NumPy的数组,对于python中的列表来说。

1. 完全不拷贝(复制整个容器对象的地址)


    >>> a = [1,2,3,[4,5,6]]
    >>> b = a            # 不会创建新的对象
    >>> id(b) ,id(a)    # a和b是同一个list对象的两个名字,所以id相同,对a的任何操作都会影响b,反之亦然
    2111502571208, 2111502571208
    >>> a[1] = 34    #会改变b的值
    >>> a[3].append(7)  #会改变b的值
    >>> b
    [ 0, 34,  3, [4,  5,  6,  7]]

2. 浅拷贝(仅仅复制了容器中元素的地址)


    >>> a = [1,2,3,[4,5,6]]
    >>> b = a[:]    #等价于b = a.copy()  ,等价于b = list(a) 创建新的对象
    >>> c = a[3]
    >>> id(a),id(b)  #不是同一个对象,所以id肯定不同
    2111502571976, 2111502570760
    >>> [id(x) for x in a]
    [1702625776, 1702625808, 1702625840, 2111640524808]
    >>> [id(x) for x in b]  #list中每个元素的id与a相同
    [1702625776, 1702625808, 1702625840, 2111640524808]
    >>> id(c)
    2111640524808
    >>> a[1] = 34    # 不会改变b和c的值
    >>> b,c
    [ 1,2,3,[4,5,6]]   [4,5,6]
    >>> a[3].append(7)   #会影响b和c的值
    >>> a
    [1, 34, 3, [4, 5, 6, 7]]
    >>> b,c
    [1,2,3,[4,5,6,7]]    [4,5,6,7]
    >>> a[3] = 90  # 指向了新的位置,此时不会再影响b与c的值

3. 深拷贝(完全拷贝了一个副本,容器内部元素地址都不一样,所以相互不影响)

    >>> import copy
    >>> a = [1,2,3,[4,5,6]]
    >>> b = copy.deepcopy(a)   #创建新的对象
    >>> id(a),id(b)     
    2111640524040, 2111639581512   
    >>> [id(x) for x in a]
    [1702625776, 1702625808, 1702625840, 2111502571976]
    >>> [id x for x in b]  # 不可变对象id值一样,可变对象id值不一样,
    #猜测可能是因为不可变对象只要值改变就会指向新位置,不会影响与其值改变
    #前指向同一位置的对象的值,而可变对象会影响,所以是这样。
    [1702625776, 1702625808, 1702625840, 2111502571976]
    >>> a[1] = 34    # 不会改变b的值
    >>> b
    [ 1,2,3,[4,5,6]]   
    >>> a[3].append(7)  # 也不会改变b的值
    >>> a
    [1, 34, 3, [4, 5, 6, 7]]
    >>> b
    [1,2,3,[4,5,6]]    
    >>> a[3] = 90  # 指向了新的位置,肯定更不会影响b的值

参考:

1.python中的可变对象与不可变对象

2.Python 直接赋值、浅拷贝和深度拷贝解析

3.谈谈python中的深拷贝和浅拷贝

4.图解Python深拷贝和浅拷贝

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值