介绍
在本实验中,你将探索 NumPy 数组操作的基础概念,特别是关注副本(copies)和视图(views)之间的区别。理解这一区别对于编写高效且无 bug 的 Python 数值代码至关重要。你将编写并执行 Python 脚本来实际应用这些概念。
在本实验中,你将探索 NumPy 数组操作的基础概念,特别是关注副本(copies)和视图(views)之间的区别。理解这一区别对于编写高效且无 bug 的 Python 数值代码至关重要。你将编写并执行 Python 脚本来实际应用这些概念。
在 NumPy 中,副本(copy)是一个拥有自己数据的全新数组,而视图(view)则是查看同一数据的另一种方式。修改视图会影响原始数组,但修改副本则不会。
让我们通过实践来了解这一点。我们将创建一个数组,然后创建它的一个视图和一个副本。接着,我们将修改它们,并观察对原始数组的影响。
首先,从左侧文件浏览器中打开 main.py 文件。然后,用以下代码替换其内容:
import numpy as np
## --- Part 1: Modifying a View ---
print("--- Modifying a View ---")
## Create an original array
original_array_view = np.array([1, 2, 3, 4, 5])
print(f"Original array: {original_array_view}")
## Create a view of the array
view_array = original_array_view.view()
## Modify the first element of the view
view_array[0] = 99
print(f"View after modification: {view_array}")
print(f"Original array after modifying the view: {original_array_view}\n")
## --- Part 2: Modifying a Copy ---
print("--- Modifying a Copy ---")
## Create another original array
original_array_copy = np.array([10, 20, 30, 40, 50])
print(f"Original array: {original_array_copy}")
## Create a copy of the array
copy_array = original_array_copy.copy()
## Modify the first element of the copy
copy_array[0] = 999
print(f"Copy after modification: {copy_array}")
print(f"Original array after modifying the copy: {original_array_copy}")
现在,保存文件并在终端中运行它以查看输出。
python main.py
你应该会看到以下输出。请注意,当视图被修改时,原始数组发生了变化,但在副本被修改时,原始数组保持不变。
--- Modifying a View ---
Original array: [1 2 3 4 5]
View after modification: [99 2 3 4 5]
Original array after modifying the view: [99 2 3 4 5]
--- Modifying a Copy ---
Original array: [10 20 30 40 50]
Copy after modification: [999 20 30 40 50]
Original array after modifying the copy: [10 20 30 40 50]
这演示了核心区别:视图与原始数据相关联,而副本则完全独立。
在 NumPy 中,一个非常常见的操作是切片(slicing),它用于从数组中选择一个元素范围。基本切片总是创建一个原始数组的视图。这是内存效率的关键特性,但你必须意识到修改切片会改变原始数据。
让我们来测试一下。清空 main.py 的内容并添加以下代码:
import numpy as np
## Create an array from 0 to 9
original_array = np.arange(10)
print(f"Original array: {original_array}")
## Create a slice of the array (elements from index 2 to 4)
array_slice = original_array[2:5]
print(f"Slice of the array: {array_slice}")
## Modify the first element of the slice
print("Modifying the first element of the slice to 100...")
array_slice[0] = 100
## Print the original array again to see the change
print(f"Original array after modification: {original_array}")
保存文件并在终端中执行它。
python main.py
你的输出将是:
Original array: [0 1 2 3 4 5 6 7 8 9]
Slice of the array: [2 3 4]
Modifying the first element of the slice to 100...
Original array after modification: [ 0 1 100 3 4 5 6 7 8 9]
正如你所见,改变 array_slice[0] 也改变了 original_array[2]。这证实了切片是一个视图,而不是一个副本。
基本切片创建视图,而高级索引(advanced indexing)总是创建一个副本。高级索引涉及传递一个列表、元组或另一个索引数组来选择元素。由于选定的元素可能不在连续的内存块中,NumPy 会创建一个新数组(副本)来存储它们。
让我们将其与前一步进行对比。清空 main.py 并插入以下代码:
import numpy as np
## Create an array from 0 to 9
original_array = np.arange(10)
print(f"Original array: {original_array}")
## Use advanced indexing to select elements at indices 1, 3, and 5
indexed_array = original_array[[1, 3, 5]]
print(f"Indexed array: {indexed_array}")
## Modify the first element of the new array
print("Modifying the first element of the indexed array to 100...")
indexed_array[0] = 100
## Print the original array again to see if it changed
print(f"Original array after modification: {original_array}")
保存文件并运行脚本。
python main.py
输出将是:
Original array: [0 1 2 3 4 5 6 7 8 9]
Indexed array: [1 3 5]
Modifying the first element of the indexed array to 100...
Original array after modification: [0 1 2 3 4 5 6 7 8 9]
这次,原始数组保持不变。indexed_array 是一个副本,因此对其的修改不会影响 original_array。
.base 识别副本和视图有时我们无法确定一个操作返回的是视图还是副本。NumPy 提供了一种可靠的检查方法:数组的 .base 属性。
.base 属性将指向它所属的原始数组对象。.base 属性将是 None。让我们用这个来确认我们之前步骤中的发现。清空 main.py 并添加以下代码:
import numpy as np
## Create an original array
original_array = np.arange(10)
print(f"Original array: {original_array}\n")
## Create a view using slicing
view_slice = original_array[2:5]
print(f"Slice (view): {view_slice}")
## Check if the slice is a view of the original array
print(f"Is the slice a view? {view_slice.base is original_array}\n")
## Create a copy using advanced indexing
copy_indexed = original_array[[1, 3, 5]]
print(f"Indexed (copy): {copy_indexed}")
## Check if the indexed array is a copy
print(f"Is the indexed array a copy? {copy_indexed.base is None}")
保存文件并在终端中运行它。
python main.py
你将得到以下输出,它以编程方式确认了切片是视图,而索引数组是副本。
Original array: [0 1 2 3 4 5 6 7 8 9]
Slice (view): [2 3 4]
Is the slice a view? True
Indexed (copy): [1 3 5]
Is the indexed array a copy? True
.base 属性是调试和确保你的代码按预期行为的宝贵工具。
在本实验中,你学习了 NumPy 数组的副本(copy)和视图(view)之间的关键区别。你练习了创建两者,并观察了修改如何影响原始数据。
.view() 和基本切片等方法会创建视图,它们内存效率高,但修改时可能导致意外的副作用。.copy() 和高级索引等方法会创建副本,它们与原始数组是独立的。.base 属性来明确检查一个数组是视图还是副本。掌握这些概念是使用 NumPy 编写健壮高效的数值应用程序的关键一步。