Numpy深拷贝、浅拷贝、视图笔记

Numpy深拷贝、浅拷贝、视图笔记

    • 浅拷贝
      • 浅拷贝中的“视图”
    • 深拷贝
      • Numpy中的“深拷贝”
    • 存取元素中的“深拷贝”与“浅拷贝”
    • “深拷贝”与“浅拷贝”的区别

拷贝就是一个变量的值传给另外一个变量。在python 中 id()方法可以查看存放变量的内存地址,这为我们理解深浅拷贝提供了便利。

浅拷贝

  • 浅拷贝:对引用的拷贝,仅仅复制了容器中元素的地址
    首先,我们先要认识赋值操作:

赋值是将一个对象的地址赋值给一个变量,让变量指向该地址( 旧瓶装旧酒 )。
修改不可变对象(str、tuple)需要开辟新的空间
修改可变对象(list等)不需要开辟新的空间

接着,用一个例子来学习浅拷贝:

import numpy as np
a = np.array([-45,-66,2,25,99,-33])
b = a
b is a
>>>True
id(a)
>>>2434667393392
id(b)#id(a)、id(b)结果相同
>>>2434667393392
a == b
>>>array([ True,  True,  True,  True,  True,  True])
a[0] = 6
a
>>>array([  6, -66,   2,  25,  99, -33])
b
>>>array([  6, -66,   2,  25,  99, -33])

从上述代码以及运行结果可以看出,共存内存地址的两个变量,当其中一个变量的值改变时,另一个变量也随之改变,此时,变量间的“拷贝”是浅拷贝。
浅拷贝只是将容器中元素的地址复制了一份,是在另一块地址中创建一个新的变量或容器,但是容器里面的元素的地址均是源对象的元素和地址的拷贝也就是所谓的“新瓶装旧酒”。

浅拷贝中的“视图”

  • ndarray.view()
    事实上,没有任何数据类型是固定的,主要取决于如何看待这片数据的内存区域。
    在numpy.ndarray.view中,提供对内存区域不同的切割方式,来完成数据类型的转换,而无须要对数据进行额外的copy,来节约内存空间。
e = np.array([-45,-66,2,25,99,-33])
f = e.view()#f与e“共享视图”
id(e)
>>>2434688036912
id(f)
>>>2434688037712
f.shape=(2,3)
f
>>>array([[-45, -66,   2],
       [ 25,  99, -33]])
e[0] = 0
e
>>>array([  0, -66,   2,  25,  99, -33])
f
>>>array([[  0, -66,   2],
       [ 25,  99, -33]])
f[1,1]=88
f
>>>array([[  0, -66,   2],
       [ 25,  88, -33]])
e
>>>array([  0, -66,   2,  25,  88, -33])     

共享“视图”(view)的两个变量,当其中一个变量的值改变时,另一个变量的值也随之改变。此时,变量间的“拷贝”也是“浅拷贝”

深拷贝

  • 深拷贝:完全拷贝了一个副本,容器内部元素地址都不一样。

深拷贝是在另一块地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的,仅仅是值相同而已,是完全的副本。也就是“新瓶装新酒 ”。

举例说明:

c = np.array([-45,-66,2,25,99,-33])
d = np.array([-45,-66,2,25,99,-33])
id(c)
>>>2434687986224
id(d)
>>>2434687986784
c == d
>>>array([ True,  True,  True,  True,  True,  True])
c[1] = 666
c
>>>array([-45, 666,   2,  25,  99, -33])
d
>>>array([-45, -66,   2,  25,  99, -33])

因此,深拷贝是完全内存拷贝,它将多维列表中的内容递归的复制一遍,重新存储到一块新的内存空间。因此在深拷贝不会出现内存公用的情况。

Numpy中的“深拷贝”

  • np.copy()
e
>>>array([  0, -66,   2,  25,  88, -33])
g = np.copy(e)
g
>>>array([  0, -66,   2,  25,  88, -33])
e[0] = 8
e
>>>array([  8, -66,   2,  25,  88, -33])
g
>>>array([  0, -66,   2,  25,  88, -33])
id(e)
>>>2434688036912
id(g)
>>>2434688007184

通过“深拷贝”得到的变量互不干扰,其中一个变量的值改变时,不影响其他变量的值。

存取元素中的“深拷贝”与“浅拷贝”

  • 使用整数序列
    当使用整数序列对数组元素进行存取时,将整数序列中的每个元素作为下标,整数序列可以是列表或者数组。使用整数序列作为下标获得的数组不和原始数组共享数据空间。
x = np.arange(10,1,-1)
x
>>>array([10,  9,  8,  7,  6,  5,  4,  3,  2])
x[[3,3,1,8]]
>>>array([7, 7, 9, 2])

此处,获取x中的下标为3,3,1,8的4个元素,组成一个新的数组。

b = x[np.array([3,3,-3,8])]
b
>>>array([7, 7, 4, 2])
b[2]=100
b
>>>array([  7,   7, 100,   2])
x
>>>array([10,  9,  8,  7,  6,  5,  4,  3,  2])
x[[3,5,1]] = -1,-2,-3
x
>>>array([10, -3,  8, -1,  6, -2,  4,  3,  2])

b的值改变,而x的值没有改变的原因是由于b和x不共享数据空间,因此x中的值并没有改变。
最后一行中,整数序列下标也可以用来修改元素的值。

  • 布尔数组
    当使用布尔数组b作为下标存取数组x中的元素时,将收集数组x中所有在数组b中对应下标为True的元素。使用布尔数组作为下标获得的数组不和原始数组共享数据空间,注意这种方式只对应于布尔数组,不能使用布尔列表。
x = np.arange(5,0,-1)
x
>>>array([5, 4, 3, 2, 1])
x[np.array([True,False,True,False,False])]
>>>array([5, 3])

布尔数组中下标为0,2的元素为True,以此获取x中下标为0,2的元素

x[np.array([True,False,True,True,False])] = -1,-2,-3
x#①
>>>array([-1,  4, -2, -3,  1])
x = np.random.rand(10)#②
x
>>>array([0.60291436, 0.66126483, 0.52988857, 0.98299845, 0.60645453,
       0.29877549, 0.29959646, 0.9257346 , 0.01818018, 0.14690692])
x[x > 0.5]
>>>array([0.60291436, 0.66126483, 0.52988857, 0.98299845, 0.60645453,
       0.9257346 ])

①处可以知道,布尔数组下标也可以用来修改元素
②处可以产生一个长度为10,元素值为0-1的随机数的数组

“深拷贝”与“浅拷贝”的区别

  1. 浅拷贝可以使用列表自带的copy()函数,或者使用copy模块的copy()函数。深拷贝只能使用copy模块的deepcopy(),所以使用前要导入:from copy import deepcopy
  2. 如果拷贝的对象里的元素只有值,没有引用,那浅拷贝和深拷贝没有差别,都会将原有对象复制一份,产生一个新对象,对新对象里的值进行修改不会影响原有对象,新对象和原对象完全分离开。
  3. 如果拷贝的对象里的元素包含引用(像一个列表里储存着另一个列表,存的就是另一个列表的引用),那浅拷贝和深拷贝是不同的,浅拷贝虽然将原有对象复制一份,但是依然保存的是引用,所以对新对象里的引用里的值进行修改,依然会改变原对象里的列表的值,新对象和原对象完全分离开并没有完全分离开。而深拷贝则不同,它会将原对象里的引用也新创建一个,即新建一个列表,然后放的是新列表的引用,这样就可以将新对象和原对象完全分离开。

你可能感兴趣的:(Numpy深拷贝、浅拷贝、视图笔记)