拷贝和视图

当对数据进行操作时,他们都数据有时被拷贝到一个新的数据,有时则没有。初学者经常会对此感到困惑。以下分三种情况介绍:

不拷贝#

简单的赋值语句将不会拷贝对象和其中的数据。

Python 将可变对象作为引用传递,因此对这类对象,函数调用将不进行拷贝。

import numpy as np

a = np.array([[ 0,  1,  2,  3],
      [ 4,  5,  6,  7],
      [ 8,  9, 10, 11]])
b = a  # 没有创建新对象
print(b is a)  # True, a 和 b 是指向同一 ndarray 对象的两个名字

def f(x):
    print(id(x))

# 以下两个两行在每次调用时打印相同的值,但具体值则不固定
print(id(a))  # 4379335184
f(a)          # 4379335184

视图或浅拷贝#

不同的数组对象可能共享相同的数据。view 方法创建一个新的数据对象,但和原数据共享相同的数据。

import numpy as np
a = np.array([[ 0,  1,  2,  3],
      [ 4,  5,  6,  7],
      [ 8,  9, 10, 11]])
c = a.view()
print(c is a)  # False
print(c.base is a)  # True,  c 是 a 所拥有的数据的一个视图
print(c.flags.owndata)  # False
c = c.reshape((2, 6))  # 改变 c 的形状并不会改变 a 的形状
print(a)
c[0, 4] = 1234  # 改变 c 的数据将同时改变 a 的数据
print(a)
# [[   0    1    2    3]
#  [1234    5    6    7]
#  [   8    9   10   11]]

切分一个数据将会返回它的一个视图。

import numpy as np

a = np.array([[ 0,  1,  2,  3],
      [ 4,  5,  6,  7],
      [ 8,  9, 10, 11]])
s = a[:, 1:3]
s[:] = 10  # s[:] 是 s 的一个视图。注意 s = 10 和 s[:] = 10 是不同的
print(a)
# [[ 0 10 10  3]
#  [ 4 10 10  7]
#  [ 8 10 10 11]]

深拷贝#

copy 是对数据及其数据的全面拷贝,被成为深拷贝

import numpy as np

a = np.array([[ 0,  1,  2,  3],
      [ 4,  5,  6,  7],
      [ 8,  9, 10, 11]])
d = a.copy()  # 创建一个新的具有独立数据的数据
print(d is a)  # False
print(d.base is a)  # False, d 和 a 不共享任何东西
d[0, 0] = 9999
print(a)  # a 还是原来的 a
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

有时在进行切分后,如果原数据已经不再需要,则有必要调用一次 copy。例如,假设 a 是一个巨型的中间结果,而最终结果 b 仅包含 a 的一小部分,那么在通过切分得到 b 后应对其执行一次深拷贝:

import numpy as np
a = np.arange(int(1e8))
b = a[:100].copy()
del a  # 释放 a 所占用的内存

要是在以上代码中使用 b = a[:100],那么 aa,即便调用了 del a,其所占用的内存仍不会释放。

形状操作广播