NumPy - 遍历数组
NumPy - 数组迭代
Section titled “NumPy - 数组迭代”在处理数组时,遍历元素是一种常见的需求。虽然标准的 Python for 循环可以用于 NumPy 数组,但对于大型数组,尤其是多维数组,它们通常效率不高。NumPy 提供了一个高度优化的迭代器对象 numpy.nditer(NumPy 迭代器对象),用于高效地进行多维迭代。
使用 np.nditer 进行基本迭代
Section titled “使用 np.nditer 进行基本迭代”np.nditer 允许你使用 Python 的标准迭代器协议(for ... in ...)访问数组中的每个元素。默认情况下,它选择与数组内存布局匹配的迭代顺序,以获得更好的性能。
示例 1:迭代 2D 数组
Section titled “示例 1:迭代 2D 数组”import numpy as np
a = np.arange(0, 60, 5).reshape(3, 4)
print(f"Original array:\n{a}\n")
print("Iterating elements using np.nditer:")for x in np.nditer(a): print(x, end=' ')print() # 换行输出(默认顺序通常是 C 风格,即行主序):
Original array:[[ 0 5 10 15] [20 25 30 35] [40 45 50 55]]
Iterating elements using np.nditer:0 5 10 15 20 25 30 35 40 45 50 55示例 2:迭代顺序与转置
Section titled “示例 2:迭代顺序与转置”默认的迭代顺序取决于数组在内存中的存储方式。迭代一个转置后的数组(a.T)可能会以不同的顺序生成元素,如果转置改变了内存的连续性。
import numpy as np
a = np.arange(0, 60, 5).reshape(3, 4)
print(f"Original array (C-order layout):\n{a}\n")
print("Transpose of the array:")b = a.T # 转置print(f"{b}\n")
print("Iterating over the transpose 'b':") # 基于底层数据,顺序可能仍然是 C 风格的for x in np.nditer(b): print(x, end=' ')print()输出:
Original array (C-order layout):[[ 0 5 10 15] [20 25 30 35] [40 45 50 55]]
Transpose of the array:[[ 0 20 40] [ 5 25 45] [10 30 50] [15 35 55]]
Iterating over the transpose 'b':0 5 10 15 20 25 30 35 40 45 50 55注意:即使 b 是转置后的数组,如果 C 风格遍历底层数据更高效,np.nditer 可能仍然选择 C 风格的遍历方式。实际的顺序取决于内存布局。
控制迭代顺序
Section titled “控制迭代顺序”你可以使用 order 参数强制 np.nditer 遵循特定的迭代顺序(C 风格/行主序 或 F 风格/列主序)。
order='C': 强制 C 风格顺序(行主序)。order='F': 强制 Fortran 风格顺序(列主序)。order='A': 如果数组是 F 连续的(F-contiguous),则以 Fortran 顺序迭代;否则以 C 顺序迭代。order='K': 按照元素在内存中的布局顺序进行迭代(保持顺序 - 默认)。
示例:C 顺序 vs F 顺序 迭代
Section titled “示例:C 顺序 vs F 顺序 迭代”import numpy as np
a = np.arange(0, 60, 5).reshape(3, 4)print(f"Original array:\n{a}\n")
print("Iterating in C-style order (row-major):")for x in np.nditer(a, order='C'): print(x, end=' ')print('\n')
print("Iterating in F-style order (column-major):")for x in np.nditer(a, order='F'): print(x, end=' ')print()输出:
Original array:[[ 0 5 10 15] [20 25 30 35] [40 45 50 55]]
Iterating in C-style order (row-major):0 5 10 15 20 25 30 35 40 45 50 55
Iterating in F-style order (column-major):0 20 40 5 25 45 10 30 50 15 35 55示例:基于内存布局的顺序
Section titled “示例:基于内存布局的顺序”如果数组以 Fortran 顺序显式创建或复制,np.nditer(使用默认的 ‘K’ 或显式的 ‘F’ 参数)将高效地进行列向迭代。
import numpy as np
a = np.arange(0, 60, 5).reshape(3, 4)b = a.T # 转置
print("Iterating 'b' (transpose) with default ('K') order:")# 仍然以 C 风格遍历原始数据布局for x in np.nditer(b): print(x, end=' ')print('\n')
print("Creating a copy of 'b' in F-order ('c = b.copy(order='F')'):")c = b.copy(order='F') # 现在 'c' 确实是 F 连续的print(f"Is 'c' F-contiguous? {c.flags.f_contiguous}")
print("Iterating 'c' with default ('K') order (now efficient F-style):")for x in np.nditer(c): print(x, end=' ')print()输出:
Iterating 'b' (transpose) with default ('K') order:0 5 10 15 20 25 30 35 40 45 50 55
Creating a copy of 'b' in F-order ('c = b.copy(order='F')'):Is 'c' F-contiguous? TrueIterating 'c' with default ('K') order (now efficient F-style):0 20 40 5 25 45 10 30 50 15 35 55迭代期间修改数组值
Section titled “迭代期间修改数组值”默认情况下,np.nditer 将数组元素视为只读。要在迭代期间修改元素,你需要指定相应的 op_flags(操作标志)参数。常用的值有 ['readonly'](默认)、['readwrite'] 或 ['writeonly']。
修改时,你需要使用省略号 (...) 来为迭代器产生的元素赋值:x[...] = value。
import numpy as np
a = np.arange(0, 60, 5).reshape(3, 4)print(f"Original array:\n{a}\n")
# 迭代并修改元素(乘以 2)for x in np.nditer(a, op_flags=['readwrite']): x[...] = 2 * x
print(f"Modified array:\n{a}")输出:
Original array:[[ 0 5 10 15] [20 25 30 35] [40 45 50 55]]
Modified array:[[ 0 10 20 30] [ 40 50 60 70] [ 80 90 100 110]]外部循环 (flags=['external_loop'])
Section titled “外部循环 (flags=['external_loop'])”flags=['external_loop'] 选项会改变迭代器的行为。它不是生成单个元素(0 维数组),而是生成数组的一维块(chunks)。这可以提高某些操作的效率,因为它减少了 Python 循环的开销。
块的大小由缓冲决定,并且迭代遵循指定的 order 参数。
使用外部循环和 F 顺序(列主序)遍历列:
import numpy as np
a = np.arange(0, 60, 5).reshape(3, 4)
print(f"Original array:\n{a}\n")
print("Iterating with external_loop (F-order yields columns as 1D arrays):")for x in np.nditer(a, flags=['external_loop'], order='F'): print(x, end=' ')print()输出:
Original array:[[ 0 5 10 15] [20 25 30 35] [40 45 50 55]]
Iterating with external_loop (F-order yields columns as 1D arrays):[ 0 20 40] [ 5 25 45] [10 30 50] [15 35 55]如果你向 np.nditer 提供多个可以广播(broadcast)到共同形状的数组,迭代器可以并发地遍历它们,生成对应的元素元组。
同时迭代一个 3x4 的数组 a 和一个 1x4 的数组 b。迭代期间,b 将被广播以匹配 a 的形状。
import numpy as np
a = np.arange(0, 60, 5).reshape(3, 4)print(f"First array 'a':\n{a}\n")
b = np.array([1, 2, 3, 4], dtype=int)print(f"Second array 'b': {b}\n")
print("Iterating over 'a' and broadcast 'b' concurrently:")for x, y in np.nditer([a, b]): print(f"{x}:{y}", end=' ')print()输出:
First array 'a':[[ 0 5 10 15] [20 25 30 35] [40 45 50 55]]
Second array 'b': [1 2 3 4]
Iterating over 'a' and broadcast 'b' concurrently:0:1 5:2 10:3 15:4 20:1 25:2 30:3 35:4 40:1 45:2 50:3 55:4