Numpy | 副本和视图


在操作数组的时候返回的不是视图就是副本。

副本:复制

视图:链接

副本是一个数据的完整拷贝,如果我们对副本进行修改,它不会影响到原始数据,物理内存不在同一位置。

视图是数据的一个别称或引用,通过该别称或引用亦可访问、操作原有数据,但原有数据不会产生拷贝。如果我们对视图进行修改,它会影响到原始数据,物理内存在同一位置。

视图一般发生在:

  • 1、numpy 的切片操作返回原数据的视图。
  • 2、调用 ndarray 的 view() 函数产生一个视图。

副本一般发生在:

  • Python 序列的切片操作,调用deepCopy()函数。
  • 调用 ndarray 的 copy() 函数产生一个副本。

赋值

赋值操作,两个变量a, b都指向同一个内存地址, b与原始数组a的id()相同。 id()返回 Python 对象的通用标识符,类似于 C 中的指针。

一个数组的任何变化都反映在另一个数组上。 例如,一个数组的形状改变也会改变另一个数组的形状。

import numpy as np

a = np.arange(6)
b = a
print('数组a:',a)
print('数组b:',b)

print('a 调用 id() 函数:',id(a))
print('b 调用 id() 函数:',id(b))

b.shape = 3, 2
print('修改 b 的形状:')
print(b)
print('a 的形状也修改了:')
print(a)

输出结果为:

数组a: [0 1 2 3 4 5]
数组b: [0 1 2 3 4 5]
a 调用 id() 函数: 2026424909056
b 调用 id() 函数: 2026424909056


修改 b 的形状:
[[0 1]
[2 3]
[4 5]]


a 的形状也修改了:
[[0 1]
[2 3]
[4 5]]

视图或浅拷贝

Numpy的切片操作会返回原数据的视图,切片产生的视图是原视图的一部分视图,对视图的修改会直接反映到原数据中,但它们的id是不同的。也就是说,视图虽然指向原数据,但是他们和赋值引用还是有区别的。

ndarray.view() 方会创建一个新的数组对象,该方法创建的新数组的维数更改不会更改原始数据的维数。

import numpy as np

# 最开始 a 是个 3X2 的数组
a = np.arange(6).reshape(3, 2)
print('数组 a:')
print(a)
print('\n')

b = a.view()
print('创建 a 的视图b:')
print(b)
print('\n')

print('a 的 id():',id(a))
print('b 的 id():',id(b))
print('\n')

# 修改 b 的形状,并不会修改 a
b.shape = 2, 3
print('修改形状后的b:')
print(b)
print('\n')

print('修改b形状后的a:')
print(a)

输出结果为:

数组 a:
[[0 1]
[2 3]
[4 5]]


创建 a 的视图b:
[[0 1]
[2 3]
[4 5]]


a 的 id(): 1601808711552
b 的 id(): 1601809504256


修改形状后的b:
[[0 1 2]
[3 4 5]]


修改b形状后的a:
[[0 1]
[2 3]
[4 5]]

使用切片创建视图修改数据会影响到原始数组:

import numpy as np

arr = np.arange(12)
print('我们的数组:',arr)
print('\n')

a = arr[3:]
b = arr[3:]
print('切片a:',a)
print('切片b:',b)
print('\n')

a[1] = 123
b[2] = 234
print('修改后的数组arr:')
print(arr)
print('修改后的数组a:')
print(a)
print('修改后的数组b:')
print(b)
print('\n')


print('a的id:',id(a))
print('b的id:',id(b))
print('arr的id:',id(arr))

输出结果为:

我们的数组: [ 0 1 2 3 4 5 6 7 8 9 10 11]


切片a: [ 3 4 5 6 7 8 9 10 11]
切片b: [ 3 4 5 6 7 8 9 10 11]


修改后的数组arr:
[ 0 1 2 3 123 234 6 7 8 9 10 11]
修改后的数组a:
[ 3 123 234 6 7 8 9 10 11]
修改后的数组b:
[ 3 123 234 6 7 8 9 10 11]


a的id: 1173376024448
b的id: 1173376023968
arr的id: 1173376023888

变量 a,b 都是 arr 的一部分视图,对视图的修改会直接反映到原数据中。但是我们观察 a,b 的 id,他们是不同的,也就是说,视图虽然指向原数据,但是他们和赋值引用还是有区别的。

副本或深拷贝

ndarray.copy() 创建一个副本。 对副本数据进行修改,不会影响到原始数据,它们物理内存不一样。

import numpy as np

a = np.array([[10, 10], [2, 3], [4, 5]])
print('数组 a:')
print(a)
print('\n')

b = a.copy()
print('创建 a 的副本数组 b:')
print(b)
print('\n')

# b 与 a 不共享任何内容
print('我们能够写入 b 来写入 a 吗?')
print(b is a)
print('\n')


b[0, 0] = 100
print('修改副本b:')
print(b)
print('\n')

print('修改副本b后,a数组不变:')
print(a)

输出结果为:

数组 a:
[[10 10]
[ 2 3]
[ 4 5]]


创建 a 的副本数组 b:
[[10 10]
[ 2 3]
[ 4 5]]


我们能够写入 b 来写入 a 吗?
False


修改副本b:
[[100 10]
[ 2 3]
[ 4 5]]


修改副本b后,a数组:
[[10 10]
[ 2 3]
[ 4 5]]

Python 中 list 的拷贝与 numpy 的 array 的拷贝

1.python中列表list的拷贝,会有什么需要注意的

Python 变量名相当于标签名。

list2=list1 直接赋值,实质上指向的是同一个内存值。任意一个变量 list1(或list2)发生改变,都会影响另一个 list2(或list1)。

切片产生新的列表,所以内存地址不一样,修改一个,不会影响另外一个。

l1=[1,2,3,4,5,6]

l2=l1
l3=l1[2:]
l4=l1[:]

l1[2]=88

print(l1)
print(l2)
print(l3)
print(l4)

输出结果:

[1, 2, 88, 4, 5, 6]
[1, 2, 88, 4, 5, 6]
[3, 4, 5, 6]
[1, 2, 3, 4, 5, 6]

而 list3 和 list4 是通过切片对 list1 的复制操作,分别指向了新的值。任意改变 list3 或 list4 的值,不会影响其他。

2.要使用 ndarray 类型的数组,需要 from numpy import * 引用工具包 numpy。

而对 ndarray 类型的数据进行拷贝时,跟 list 类型有一点区别。

数组切片是原始数组的视图,这意味着数据不会被复制,视图上的任何修改都会被直接反映到源数组上。

array1, array2, array3, array4 实际指向同一个内存值,任意修改其中的一个变量,其他变量值都会被修改。

若想要得到的是 ndarray 切片的一份副本而非视图,就需要显式的进行复制操作函数 copy()。

import numpy as np

l =  [1,2,3,4,5,6]
arr1 = np.asarray(l)

# 对原始的 array1 的复制
arr2 = arr1
arr3 = arr1.copy()


# 对切片 array1[1:4] 的复制
arr4 = arr1[1:4].copy()

# 改变a中第一个元素的值
arr1[2] = 100

print(arr1)
print(arr2)
print(arr3)
print(arr4)

结果输出:

[ 1 2 100 4 5 6]
[ 1 2 100 4 5 6]
[1 2 3 4 5 6]
[2 3 4]

那么,修改 arr3 或 arr4,就不会影响 arr1。可以看到,改变 arr1 后,arr2 的值也跟着变了,这是为什么呢?

实际上,变量 arr1 中并没有存储任何的值,它只是指向了一个内存地址,而这个地址里存储着 array 具体的内容,当把arr1赋值给arr2的时候,实际上是把 arr1 指向内存中某对象的链接赋给了arr2,也就是说,现在 arr1 和 arr2 都指向了同一个对象。因此,在改变了内存中 array 的值后,而 arr1 与 arr2 都引用了该 array 对象,所以都一起发生了变化。这种将内存引用赋值给另一个变量的操作叫做浅拷贝。

那什么是深拷贝呢,其实就是在赋值的时候,不把同一个内存对象的引用赋值给另一个变量,令两个变量所指向的对象不一样,更改值的时候不相互影响,这种操作就是深拷贝。