NumPy - 复制与视图
NumPy - 副本(Copies)与视图(Views)
Section titled “NumPy - 副本(Copies)与视图(Views)”在使用 NumPy 数组时,某些操作会返回原始数组的“副本”,而另一些则返回“视图”。理解它们之间的区别至关重要,这有助于编写高效且无 bug 的代码。
**副本(Copy)**意味着数据在另一个内存位置被物理复制。对副本的更改不会影响原始数组,反之亦然。
另一方面,视图(View)提供了对相同内存内容的不同视角。通过视图进行的更改会影响原始数组,并且对原始数组的更改可能会影响视图。可以将视图视为查看相同数据的不同镜头。
无副本(赋值)
Section titled “无副本(赋值)”简单的赋值操作(b = a)不会创建数组或其数据的副本。它使得两个变量引用完全相同的内存中的 NumPy ndarray 对象。您可以使用 Python 内置的 id() 函数来验证这一点,该函数返回对象的唯一标识符(类似于内存地址)。
由于两个变量指向同一个对象,通过一个变量进行的任何修改(例如改变形状或元素)都将立即反映在使用另一个变量访问数组时。
import numpy as np
a = np.arange(6)print(f"Original array 'a':\n{a}")print(f"ID of 'a': {id(a)}\n") # ID 会有所不同
print("Assigning 'b = a'")b = aprint(f"Array 'b':\n{b}")print(f"ID of 'b': {id(b)} (Same as 'a')\n") # 与 'a' 相同
print("Changing shape of 'b' to (3, 2)")b.shape = (3, 2)print(f"New shape of 'b':\n{b}\n") # 'b' 的新形状
print("Shape of 'a' also changes:") # 'a' 的形状也随之改变print(a)输出:
Original array 'a':[0 1 2 3 4 5]ID of 'a': 140189398520688 # ID will vary
Assigning 'b = a'Array 'b':[0 1 2 3 4 5]ID of 'b': 140189398520688 (Same as 'a')
Changing shape of 'b' to (3, 2)New shape of 'b':[[0 1] [2 3] [4 5]]
Shape of 'a' also changes:[[0 1] [2 3] [4 5]]视图(浅拷贝)
Section titled “视图(浅拷贝)”视图创建一个新的 ndarray 对象,但它与原始数组共享底层数据缓冲区。NumPy 的 ndarray.view() 方法显式地创建一个视图。
视图的关键特性:
- 视图有自己的对象标识(
id(a) != id(b))。 - 视图与原始数组共享数据。在视图中修改元素会改变原始数组,反之亦然。
- 视图可以具有不同的形状(
shape)、跨度(strides)和数据类型(如果兼容),而不会影响原始数组的形状或 dtype 属性。但是,它们仍然指向相同的内存。
获得视图的一种非常常见的方式是通过切片(slicing)。
示例:使用 view()
Section titled “示例:使用 view()”import numpy as np
# 从一个 3x2 的数组开始a = np.arange(6).reshape(3, 2)print(f"Original array 'a':\n{a}")print(f"ID of 'a': {id(a)}\n") # ID 会有所不同
print("Creating view 'b = a.view()'")b = a.view()print(f"View 'b':\n{b}")print(f"ID of 'b': {id(b)} (Different from 'a')\n") # 与 'a' 不同
# 改变视图 'b' 的形状。这不会改变 'a'.shape。# Change the shape of the view 'b'. This does NOT change 'a'.shape.b.shape = (2, 3)print(f"Shape of 'b' changed:\n{b}") # 'b' 的形状已改变print(f"Shape of 'a' remains unchanged:\n{a}\n") # 'a' 的形状保持不变
# 修改视图 'b' 中的一个元素# Modify an element in the view 'b'print("Modifying b[0, 0] = 99") # 修改 b[0, 0] = 99b[0, 0] = 99print(f"View 'b' after modification:\n{b}") # 修改后的视图 'b'print(f"Original array 'a' is also affected:\n{a}") # 原始数组 'a' 也受到了影响输出:
Original array 'a':[[0 1] [2 3] [4 5]]ID of 'a': 140189398521008 # ID will vary
Creating view 'b = a.view()'View 'b':[[0 1] [2 3] [4 5]]ID of 'b': 140189398521152 # ID will vary (Different from 'a')
Shape of 'b' changed:[[0 1 2] [3 4 5]]Shape of 'a' remains unchanged:[[0 1] [2 3] [4 5]]
Modifying b[0, 0] = 99View 'b' after modification:[[99 1 2] [ 3 4 5]]Original array 'a' is also affected:[[99 1] [ 2 3] [ 4 5]]示例:切片创建视图
Section titled “示例:切片创建视图”基本的切片操作在 NumPy 中通常返回视图。这是一种性能优化,但如果您期望获得一个副本,可能会感到意外。
import numpy as np
a = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
print(f"Original array 'a':\n{a}\n")
print("Create a slice 's = a[:2, 1:]' (first 2 rows, columns from index 1 onwards)") # 创建切片 's = a[:2, 1:]'(前两行,从索引 1 开始的列)s = a[:2, 1:] # 选中 [[20, 30], [50, 60]]print(f"Slice 's':\n{s}\n")
print("Modify s[0, 0] = 999") # 修改 s[0, 0] = 999s[0, 0] = 999 # 修改切片的第一个元素
print(f"Slice 's' after modification:\n{s}") # 修改后的切片 's'print(f"Original array 'a' is also modified:\n{a}") # 原始数组 'a' 也受到了影响输出:
Original array 'a':[[ 10 20 30] [ 40 50 60] [ 70 80 90]]
Create a slice 's = a[:2, 1:]' (first 2 rows, columns from index 1 onwards)Slice 's':[[20 30] [50 60]]
Modify s[0, 0] = 999Slice 's' after modification:[[999 30] [ 50 60]]Original array 'a' is also modified:[[ 10 999 30] [ 40 50 60] [ 70 80 90]]深拷贝(Deep Copy)
Section titled “深拷贝(Deep Copy)”深拷贝,使用 ndarray.copy() 方法创建,它会复制数组对象及其数据。新数组与原始数组完全独立。
当您需要修改一个新数组而不影响原始数组时,请使用深拷贝。
import numpy as np
a = np.array([[10, 10], [ 2, 3], [ 4, 5]])
print(f"Original array 'a':\n{a}")print(f"ID of 'a': {id(a)}\n") # ID 会有所不同
print("Create a deep copy 'b = a.copy()'") # 创建一个深拷贝 'b = a.copy()'b = a.copy()print(f"Array 'b':\n{b}")print(f"ID of 'b': {id(b)} (Different from 'a')\n") # 与 'a' 不同
# 检查 'b' 是否与 'a' 共享内存# Check if 'b' shares memory with 'a'# np.shares_memory() 是更直接的检查方法# np.shares_memory() is a more direct way to check thisprint(f"Does 'b' share memory with 'a'? {np.shares_memory(a, b)}")# 'is' 运算符检查对象标识,对于副本来说会是 False。# The 'is' operator checks object identity, which will be False for a copy.print(f"Is 'b' the same object as 'a'? {b is a}\n")
print("Change the contents of 'b': b[0, 0] = 100") # 改变 'b' 的内容: b[0, 0] = 100b[0, 0] = 100
print(f"Modified array 'b':\n{b}\n") # 修改后的数组 'b'
print(f"Array 'a' remains unchanged:\n{a}") # 数组 'a' 保持不变输出:
Original array 'a':[[10 10] [ 2 3] [ 4 5]]ID of 'a': 140189398521568 # ID will vary
Create a deep copy 'b = a.copy()'Array 'b':[[10 10] [ 2 3] [ 4 5]]ID of 'b': 140189398521712 # ID will vary (Different from 'a')
Does 'b' share memory with 'a'? FalseIs 'b' the same object as 'a'? False
Change the contents of 'b': b[0, 0] = 100Modified array 'b':[[100 10] [ 2 3] [ 4 5]]
Array 'a' remains unchanged:[[10 10] [ 2 3] [ 4 5]]**关键点:**在使用数组时,请注意操作返回的是视图还是副本,尤其是在修改数组时。如果您需要独立性,请明确使用 .copy()。