NumPy - 复制与视图



在 NumPy 中,当您对数组执行操作时,结果可能是原始数据的副本或只是原始数据的视图。了解这两者之间的区别对于有效的内存管理以及避免代码中的意外副作用非常重要。

在 NumPy 中创建副本

我们可以使用 copy() 函数在 NumPy 中显式地创建数组的副本。此函数生成一个新数组,并将原始数组中的数据复制到此新数组中。

当您在 NumPy 中创建数组的副本时,数据将被完全复制。这意味着对副本进行的更改不会影响原始数组,反之亦然。当您需要使用修改后的数组版本而不更改原始数据时,副本很有用。

示例

在以下示例中,修改copied_array不会影响original_array,这证明了这两个数组的独立性:

import numpy as np

# Original array
original_array = np.array([1, 2, 3])

# Creating a copy
copied_array = original_array.copy()

# Modifying the copy
copied_array[0] = 10

print("Original Array:", original_array)  
print("Copied Array:", copied_array)     

获得以下输出:

Original Array: [1 2 3]
Copied Array: [10  2  3]

浅拷贝与深拷贝

在 NumPy 数组的上下文中,浅拷贝和深拷贝之间的区别对于理解复制数据的方式很重要。

浅拷贝

数组的浅拷贝创建一个新的数组对象,但如果这些元素本身是数组或其他复杂对象,则它不会创建原始数组中包含的元素的副本。

相反,新数组仍然引用与原始数组相同的元素。这意味着对元素内容的更改将影响原始数组和复制数组。

  • 数组级副本 - 在 NumPy 数组的情况下,浅拷贝意味着虽然复制了顶层数组对象,但底层数据缓冲区没有被复制。新数组只是同一数据的新的视图。
  • 用法 - 当您需要一个新的数组对象但又想避免复制大量数据的开销时,浅拷贝很有用。

示例

在此示例中,修改shallow_copy也会修改original_array,因为它们共享相同的基础数据:

import numpy as np

# Original array
original_array = np.array([[1, 2, 3], [4, 5, 6]])

# Shallow copy
shallow_copy = original_array.view()

# Modify an element in the shallow copy
shallow_copy[0, 0] = 100

print("Original Array:")
print(original_array)

print("\nShallow Copy:")
print(shallow_copy)    

这将产生以下结果:

Original Array:
[[100   2   3]
 [  4   5   6]]

Shallow Copy:
[[100   2   3]
 [  4   5   6]]

深拷贝

另一方面,深拷贝会创建一个新的数组对象以及它包含的所有数据的副本。这意味着对新数组进行的任何更改都不会影响原始数组,反之亦然。新数组中的数据与原始数组中的数据完全独立。

  • 完全复制 - 在 NumPy 的上下文中,深拷贝涉及复制数组的整个数据缓冲区,以确保新数组与原始数组完全分离。
  • 用法 - 当您需要独立于原始数组使用数据时,深拷贝非常重要,尤其是在数据可能以不应影响原始数据的方式修改时。

示例

在这种情况下,修改deep_copy不会影响original_array,这证明了这两个数组的独立性:

import numpy as np

# Original array
original_array = np.array([[1, 2, 3], [4, 5, 6]])

# Deep copy
deep_copy = original_array.copy()

# Modify an element in the deep copy
deep_copy[0, 0] = 100

print("Original Array:")
print(original_array)

print("\nDeep Copy:")
print(deep_copy)   

以下是上述代码的输出:

Original Array:
[[1 2 3]
[4 5 6]]

Deep Copy:
[[100   2   3]
 [  4   5   6]]

复制子数组

为了避免在处理子数组时修改原始数组,您应该创建子数组的副本。当您需要独立于原始数据操作或分析子数组时,这很有用。

子数组只是现有 NumPy 数组的一部分。您可以使用切片技术提取子数组。

例如,如果您有一个二维数组,您可以通过沿着其行和列进行切片来提取一个较小的二维子数组。但是,默认情况下,切片会创建原始数组的视图,而不是单独的副本。这意味着对子数组的更改也将影响原始数组,除非您显式地创建副本。

示例

在下面的示例中,sub_array由于使用了 copy() 函数,因此是一个完全独立的数组:

import numpy as np

# Original 2D array
original_array = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# Creating a copy of the subarray
sub_array = original_array[0:2].copy()
sub_array[0] = 20

print("Original Array after subarray copy:", original_array)  
print("Subarray:", sub_array)                                 

获得的输出如下所示:

Original Array after subarray copy: 
[[1 2 3]
 [4 5 6]
 [7 8 9]]
Subarray: 
[[20 20 20]
 [ 4  5  6]]

在 NumPy 中创建视图

当您切片数组或执行某些操作(如重塑)时会创建视图。数据不会被复制;相反,新数组只是查看原始数据的一种不同方式。

换句话说,视图是一个新的数组对象,它查看与原始数组相同的数据。这意味着,如果您修改视图,更改将反映在原始数组中,反之亦然。

示例

在此示例中,修改view_array直接影响original_array,表明它们共享相同的数据:

import numpy as np

# Original array
original_array = np.array([1, 2, 3])

view_array = original_array[0:2]

# Modifying the view
view_array[0] = 30

print("Original Array after view modification:", original_array) 
print("View Array:", view_array)                                   

执行上述代码后,我们将获得以下输出:

Original Array after view modification: [30  2  3]
View Array: [30  2]

何时返回视图?

并非所有切片或操作都会导致视图。如果数组的内存布局发生变化,NumPy 可能会返回副本而不是视图。

来自切片的视图

NumPy 中返回视图的最常见情况是当您切片数组时。切片是通过指定一系列索引来提取数组一部分的方法。NumPy 返回一个视图,而不是创建一个具有自身数据的新数组,这意味着切片数组与原始数组共享相同的数据。

示例

在此示例中,view_arrayoriginal_array的视图。数据不会被复制,并且两个数组共享相同的底层内存。这意味着对“view_array”进行的任何更改也将影响“original_array”:

import numpy as np

# Original array
original_array = np.array([1, 2, 3, 4, 5])

# Creating a view by slicing the original array
view_array = original_array[1:4]

print("Original Array:")
print(original_array)

print("\nView Array (Sliced):")
print(view_array)

产生的结果如下:

Original Array:
[1 2 3 4 5]

View Array (Sliced):
[2 3 4]

来自重塑的视图

返回视图的另一种常见情况是当您重塑数组时。重塑会更改数组的形状(即每个维度中的元素数),而不更改底层数据。在可能的情况下,NumPy 会以新形状返回原始数组的视图。

示例

这里,reshaped_arrayoriginal_array的视图,只是以“2x3”格式呈现。数据保持不变,修改“reshaped_array”也会修改“original_array”:

import numpy as np

# Original 1D array
original_array = np.array([1, 2, 3, 4, 5, 6])

# Reshaping the array into a 2x3 matrix
reshaped_array = original_array.reshape(2, 3)

print("Original Array:")
print(original_array)

print("\nReshaped Array (View):")
print(reshaped_array)

我们获得以下输出:

Original Array:[1 2 3 4 5 6]
Reshaped Array (View):
[[1 2 3]
 [4 5 6]]

来自转置的视图

转置数组涉及将其翻转过其对角线,将行转换为列,反之亦然。当您使用np.transpose()函数或.T属性转置数组时,NumPy 会在可能的情况下返回视图,而不是副本。

示例

在这种情况下,transposed_arrayoriginal_array的视图,但轴已交换。底层数据保持不变,对“transposed_array”的更改将反映在“original_array”中:

import numpy as np

# Original 2D array
original_array = np.array([[1, 2, 3], [4, 5, 6]])

# Transposing the array
transposed_array = original_array.T

print("Original Array:")
print(original_array)

print("\nTransposed Array (View):")
print(transposed_array)

获得以下输出:

Original Array:
[[1 2 3]
 [4 5 6]]

Transposed Array (View):
[[1 4]
 [2 5]
 [3 6]]

base 属性

在 NumPy 中,数组的base属性检查数组是另一个数组的视图还是副本。它是对从中派生当前数组的原始数组的引用。

如果当前数组是另一个数组的视图,“base”将指向该原始数组。如果当前数组不是视图(即它是原始数组或深拷贝),则“base”将为None

示例:原始数组的 base 属性

当您创建数组时,其 base 属性将为 None,因为它就是原始数组:

import numpy as np

# Creating an original array
original_array = np.array([10, 20, 30, 40, 50])

# Checking the base attribute
print("Base of original array:", original_array.base)

这将产生以下结果:

Base of original array: None

示例:视图的 base 属性

当您创建数组的视图(例如,通过切片)时,视图的 base 属性将指向原始数组:

import numpy as np

# Creating an original array
original_array = np.array([10, 20, 30, 40, 50])

# Creating a view of the original array
view_array = original_array[1:4]

# Checking the base attribute
print("Base of view array:", view_array.base)

以下是上述代码的输出:

Base of view array: [10 20 30 40 50]

示例:副本的 base 属性

如果您创建数组的副本,则 base 属性将为 None,表示复制的数组独立于原始数组:

import numpy as np

# Creating an original array
original_array = np.array([10, 20, 30, 40, 50])

# Creating a copy of the original array
copy_array = original_array.copy()

# Checking the base attribute
print("Base of copy array:", copy_array.base)

以下是上述代码的输出:

Base of copy array: None
广告