NumPy数组创建全攻略:从基础函数到性能优化实战
1. 从零开始为什么数组是数据处理的基石如果你刚开始接触Python数据分析或者科学计算第一个绕不开的库大概率就是Numpy。而学习Numpy第一个要啃下的硬骨头就是“创建数组”。很多人可能会觉得这不就是几个函数调用吗有什么好讲的但恰恰是这第一步决定了你后续所有操作的效率、内存占用甚至是代码的优雅程度。我见过太多新手一上来就用Python原生的列表list去模拟矩阵运算写出来的代码又慢又臃肿一个简单的求和都要循环嵌套效率低得令人发指。而一旦你掌握了Numpy数组的正确创建方式就相当于拿到了进入高效数值计算世界的钥匙。Numpy的核心是ndarrayN-dimensional arrayN维数组对象。你可以把它理解为一个超级加强版的、同质化的所有元素类型必须相同数据容器。它和Python列表最本质的区别在于列表里可以装任何类型的数据整数、字符串、甚至另一个列表像一个收纳杂物的抽屉而Numpy数组更像一个整齐划一的军队方阵所有“士兵”数据元素类型一致并且紧密地排列在连续的内存块中。正是这种设计使得Numpy能够利用底层C语言的优化和向量化指令如SIMD实现成百上千倍的性能提升。所以学习创建数组绝不仅仅是记住np.array()这么简单。你需要理解在什么场景下该用哪种创建方法如何从一开始就指定好数据的类型dtype以避免后续隐式转换带来的性能损失和潜在bug如何快速生成测试数据或特定模式的数组这些问题的答案都藏在不同的数组创建函数里。接下来我们就抛开那些枯燥的文档式罗列从一个实践者的角度深入聊聊Numpy数组创建的“道”与“术”。2. 基石方法np.array()的深度解析与实战陷阱np.array()是大多数人学会的第一个Numpy函数它负责将类似数组的数据结构主要是Python列表和元组转换为Numpy数组。它的语法看起来很简单np.array(object, dtypeNone, copyTrue, ...)。但每个参数背后都藏着需要你注意的细节。2.1 基础转换与维度推断最直接的用法就是传入一个列表。Numpy会自动推断数据的维度和类型。import numpy as np # 一维数组 arr_1d np.array([1, 2, 3, 4, 5]) print(arr_1d) # 输出[1 2 3 4 5] print(arr_1d.shape) # 输出(5,) 表示这是一个包含5个元素的一维数组 print(arr_1d.dtype) # 输出int64 (取决于你的系统可能是int32) # 二维数组列表的列表 arr_2d np.array([[1, 2, 3], [4, 5, 6]]) print(arr_2d) # 输出 # [[1 2 3] # [4 5 6]] print(arr_2d.shape) # 输出(2, 3) 表示2行3列这里有一个新手常犯的错误试图用长度不一致的嵌套列表创建“不规则”数组。Numpy数组要求每个维度上的长度必须一致它本质上是一个矩形的数据区域。# 错误示例试图创建“不规则”数组 try: bad_arr np.array([[1, 2], [3, 4, 5]]) # 第二行有3个元素第一行只有2个 except ValueError as e: print(f错误信息{e}) # 输出错误信息setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (2,) inhomogeneous part.Numpy会报错因为它无法确定一个统一的形状shape。最终生成的会是一个包含Python列表对象的一维数组dtypeobject这完全丧失了Numpy的性能优势应绝对避免。注意np.array()会尽可能地为输入数据推断一个合适的数值类型dtype。如果列表中混入了浮点数整个数组会被提升为浮点型。2.2 关键参数dtype性能与精度的掌控者dtype参数是np.array()的灵魂它直接决定了数组在内存中的存储方式、计算精度和速度。不指定dtypeNumpy会自己猜但它的猜测可能不符合你的预期或需求。# 情况1默认推断 arr_default np.array([1, 2, 3.0]) # 列表中包含浮点数 print(arr_default.dtype) # 输出float64。整数被“提升”为浮点数。 # 情况2显式指定强制类型 arr_int np.array([1.2, 2.7, 3.9], dtypenp.int32) # 指定为32位整数 print(arr_int) # 输出[1 2 3]。发生了截断小数部分丢失 print(arr_int.dtype) # 输出int32 # 情况3更精确的控制 arr_float32 np.array([1, 2, 3], dtypenp.float32) # 指定为32位浮点 print(arr_float32.dtype) # 输出float32为什么需要关心dtype内存占用一个int8字节类型的数组元素只占1字节而float64双精度浮点占8字节。处理百万、千万级数据时内存差异是巨大的。计算速度通常位数越低的类型如float32比float64计算越快尤其是在GPU上。精度要求科学计算中可能需要float64甚至float128来保证数值稳定性图像处理中uint80-255就足够了。兼容性与其他库如OpenCV、TensorFlow交互时对dtype有严格要求。我的实战心得在创建数组时养成显式指定dtype的习惯。即使数据源是整数如果你后续要进行除法等可能产生小数的运算不如一开始就创建为float32或float64避免中间产生不必要的类型转换副本。2.3 参数copy视图与副本的微妙博弈这是另一个容易埋坑的参数。copyTrue默认意味着创建输入数据的一个完整副本copyFalse则意味着尽可能返回一个视图view。视图和原数据共享底层数据内存修改视图会影响原数据。original_list [1, 2, 3, 4, 5] # 默认 copyTrue创建副本 arr_copy np.array(original_list, copyTrue) arr_copy[0] 999 print(original_list) # 输出[1, 2, 3, 4, 5]。原列表未受影响。 # 设置 copyFalseNumpy会尝试创建视图但不总是成功 # 对于Python列表即使copyFalseNumpy通常也必须复制数据因为列表内存布局与数组不同。 # 但对于已经是Numpy数组的输入效果就明显了 original_arr np.array([1, 2, 3]) arr_view np.array(original_arr, copyFalse) # 或 original_arr.view() arr_view[0] 999 print(original_arr) # 输出[999 2 3]。原数组被修改了什么时候用copyFalse当你明确需要创建一个与现有数组共享数据的新数组对象比如改变数组的维度或数据类型但不想复制底层数据时。这可以节省大量内存和复制时间。但对于从Python列表创建数组copyFalse通常无效因为内存布局必须重构。避坑指南除非你非常清楚自己在做什么并且需要极致优化否则在从非Numpy数据源如列表创建数组时保持copyTrue的默认值是最安全的选择可以避免难以追踪的副作用。3. 高效初始化告别循环用函数快速构建数组在实际项目中我们很少手动一个个数字去写列表。更多时候我们需要快速生成具有特定规律或形状的数组比如全零的矩阵、单位矩阵、等差数列等。Numpy提供了一系列高效的初始化函数。3.1 生成固定值数组zeros,ones,full这几个函数用于生成所有元素为相同值的数组。# 生成一个3行4列的全零浮点数矩阵 zeros_arr np.zeros((3, 4)) # 注意参数是一个表示形状的元组 (3, 4) print(zeros_arr) # 输出 # [[0. 0. 0. 0.] # [0. 0. 0. 0.] # [0. 0. 0. 0.]] # 生成一个长度为5的全1向量并指定类型为整数 ones_arr np.ones(5, dtypenp.int32) # 形状可以直接用整数表示一维 print(ones_arr) # 输出[1 1 1 1 1] # 生成一个2x2x3的三维数组并用7填充 full_arr np.full((2, 2, 3), fill_value7) print(full_arr) # 输出 # [[[7 7 7] # [7 7 7]] # [[7 7 7] # [7 7 7]]]应用场景np.zeros初始化神经网络的权重矩阵、作为累加器的初始状态。np.ones在需要计算乘积或作为乘法单位元时使用。np.full初始化一个具有特定占位符如-1或一个很大的数的数组。3.2 生成序列数组arange,linspace,logspace这些函数用于生成具有规律变化的数值序列。np.arange(start, stop, step)类似于Python的range()但生成的是数组。注意区间是[start, stop)左闭右开。arr_range np.arange(0, 10, 2) # 从0开始到10之前不包括10步长为2 print(arr_range) # 输出[0 2 4 6 8] # 浮点数步长也可以但要注意浮点精度问题 arr_float_range np.arange(0, 1, 0.2) print(arr_float_range) # 输出[0. 0.2 0.4 0.6 0.8]np.linspace(start, stop, num)在指定的区间内生成等间隔的num个点。注意区间是[start, stop]默认闭区间。arr_lin np.linspace(0, 1, 5) # 在0到1之间包括0和1生成5个等差点 print(arr_lin) # 输出[0. 0.25 0.5 0.75 1.]linspace和arange是新手最容易混淆的两个函数。记住一个简单的区分arange关心步长linspace关心点的数量。当你需要固定数量的样本点比如绘图时的x坐标时用linspace当你需要以固定步长递增时用arange。np.logspace(start, stop, num, base10)生成在对数尺度上等间隔的数。例如np.logspace(0, 2, 3)生成的是[10^0, 10^1, 10^2]即[1, 10, 100]。这在需要测试跨越多个数量级的参数时非常有用比如学习率搜索。3.3 生成特殊矩阵eye,identity,diagnp.eye(N, MNone, k0)生成一个N行M列的单位矩阵对角线为1其余为0。M默认为N生成方阵。k参数控制对角线的偏移正数向上偏移负数向下偏移。# 3x3单位矩阵 I3 np.eye(3) print(I3) # 输出 # [[1. 0. 0.] # [0. 1. 0.] # [0. 0. 1.]] # 3x4矩阵主对角线向上偏移1的位置为1 eye_k np.eye(3, 4, k1) print(eye_k) # 输出 # [[0. 1. 0. 0.] # [0. 0. 1. 0.] # [0. 0. 0. 1.]]np.identity(n)生成n维方阵的单位矩阵功能相当于np.eye(n)但更语义化。np.diag(v, k0)如果v是一维数组则将其作为对角线元素生成一个方阵如果v是二维数组则提取其对角线元素。# 从一维数组创建对角阵 arr_1d np.array([1, 2, 3]) diag_from_1d np.diag(arr_1d) print(diag_from_1d) # 输出 # [[1 0 0] # [0 2 0] # [0 0 3]] # 从二维数组提取对角线 arr_2d np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) diag_from_2d np.diag(arr_2d) print(diag_from_2d) # 输出[1 5 9]4. 高级构造与随机数组应对复杂场景除了规整的数组我们还需要能生成随机数据、模拟真实场景或者从现有数据中灵活构造新数组。4.1 随机数组生成np.random模块Numpy的随机模块是数据科学和机器学习的“数据工厂”。最常用的几个函数# 设置随机种子保证结果可复现在调试和分享代码时非常重要 np.random.seed(42) # 1. 生成[0.0, 1.0)之间的均匀分布随机浮点数 rand_uniform np.random.rand(2, 3) # 形状参数直接传入不是元组 print(rand_uniform) # 输出示例因种子固定 # [[0.37454012 0.95071431 0.73199394] # [0.59865848 0.15601864 0.15599452]] # 2. 生成标准正态分布均值为0标准差为1的随机数 rand_normal np.random.randn(2, 3) print(rand_normal) # 输出示例 # [[-0.46947439 0.54256004 -0.46341769] # [-0.46572975 0.24196227 -1.91328024]] # 3. 生成指定范围内的随机整数 rand_int np.random.randint(low10, high20, size(3, 4)) # [10, 20) 区间 print(rand_int) # 输出示例 # [[15 18 11 16] # [14 10 12 19] # [13 17 10 18]] # 4. 从给定的一维数组中随机选择可放回/不放回 choices np.array([A, B, C, D]) random_choice np.random.choice(choices, size5, replaceTrue) # replaceTrue允许重复 print(random_choice) # 输出示例[B A D B C]重要更新从Numpy 1.17开始推荐使用新的随机数生成器RNG体系它更灵活且功能隔离更好。# 现代推荐写法 rng np.random.default_rng(seed42) # 创建一个随机数生成器实例 rand_modern rng.random((2, 3)) # 相当于旧的 rand normal_modern rng.standard_normal((2, 3)) # 相当于旧的 randn int_modern rng.integers(low10, high20, size(3, 4)) # 相当于旧的 randint4.2 从字节或缓冲区创建np.frombuffer与np.fromstring这两个函数用于从已有的二进制缓冲区或字节字符串直接创建数组视图view无需复制数据效率极高。常用于与其它底层库如图像处理、网络传输交互。# 示例从字节串创建数组注意dtype必须匹配 byte_data b\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00 # 小端序的4字节整数 1, 2, 3 arr_from_buffer np.frombuffer(byte_data, dtypenp.int32) # 告诉Numpy如何解释这些字节 print(arr_from_buffer) # 输出[1 2 3] # np.fromstring 类似但用于字符串已不推荐建议用 frombuffer str_data 1 2 3 4 5 arr_from_string np.fromstring(str_data, dtypeint, sep ) print(arr_from_string) # 输出[1 2 3 4 5]警告np.fromstring这个名字容易误解它其实期望的是字符串形式的文本数字用分隔符隔开而不是二进制字节串。对于二进制数据务必使用np.frombuffer。并且要极度小心dtype和字节序endianness的匹配否则读出来的数据是错的。4.3 网格坐标生成np.meshgrid与np.mgrid/np.ogrid在需要计算二维或三维空间上每个点的函数值时比如绘制3D曲面图我们需要生成坐标网格。meshgrid是最常用的工具。# 假设我们有x轴和y轴的坐标点 x np.linspace(-2, 2, 5) y np.linspace(-1, 1, 3) print(x:, x) # [-2. -1. 0. 1. 2.] print(y:, y) # [-1. 0. 1.] # 生成网格坐标矩阵 X, Y np.meshgrid(x, y) print(X (网格中每个点的x坐标):) print(X) # 输出 # [[-2. -1. 0. 1. 2.] # [-2. -1. 0. 1. 2.] # [-2. -1. 0. 1. 2.]] print(Y (网格中每个点的y坐标):) print(Y) # 输出 # [[-1. -1. -1. -1. -1.] # [ 0. 0. 0. 0. 0.] # [ 1. 1. 1. 1. 1.]] # 现在可以计算每个网格点上的值例如 z x^2 y^2 Z X**2 Y**2 print(Z:) print(Z)mgrid和ogrid是更简洁的网格生成器它们使用切片语法。mgrid返回密集网格类似meshgrid的indexingij模式而ogrid返回开放网格节省内存适用于广播。# 使用 mgrid语法类似切片 [start:stop:step] X_m, Y_m np.mgrid[-2:2:5j, -1:1:3j] # 注意用5j表示生成5个点是复数语法 print(X_m.shape, Y_m.shape) # (5, 3) (5, 3) # 使用 ogrid生成的是可广播的一维数组 X_o, Y_o np.ogrid[-2:2:5j, -1:1:3j] print(X_o.shape, Y_o.shape) # (5, 1) (1, 3)。通过广播它们可以代表整个网格。5. 性能优化与内存布局创建数组的“隐藏维度”对于大规模数据处理数组的创建方式不仅影响代码简洁性更直接影响性能。这里涉及两个关键概念预分配和内存布局。5.1 预分配数组避免在循环中动态增长这是新手写出低效代码的重灾区。千万不要在循环内部使用np.append、np.concatenate等函数来“扩展”数组。# 错误示范低效的动态扩展 result_list [] for i in range(10000): # 模拟一些计算产生一个长度为10的向量 data_chunk np.random.randn(10) result_list.append(data_chunk) # 先放到Python列表里 result np.array(result_list) # 循环结束后一次性转换 # 或者更糟的做法在循环里不断用 np.append上面的代码中np.append实际上每次都会创建一个全新的数组并复制所有数据时间复杂度是O(N²)数据量大时慢得无法忍受。正确的做法是预分配# 正确示范预分配数组 num_iterations 10000 chunk_size 10 # 预先分配好最终大小的数组用空值如0或NaN填充 result_preallocated np.zeros((num_iterations, chunk_size)) # 或者用 np.empty 更快但里面是未初始化的内存垃圾 # result_preallocated np.empty((num_iterations, chunk_size)) for i in range(num_iterations): data_chunk np.random.randn(chunk_size) result_preallocated[i, :] data_chunk # 直接赋值到预分配的位置如果无法提前知道最终大小一个折中的高效方案是先使用Python列表在循环中收集数据列表的append操作是O(1)摊销复杂度循环结束后再一次性转换为Numpy数组。如上面错误示范中的result_list用法这比在Numpy数组上反复append要好得多。5.2 理解内存布局order参数与数组重塑Numpy数组在内存中是以连续块存储的。多维数组有两种主要的存储顺序‘C’ (行优先C-style)最后一个轴axis变化最快。例如对于一个2维数组在内存中是一行接一行存储的。这是Numpy和C语言的默认方式。‘F’ (列优先Fortran-style)第一个轴变化最快。在内存中是一列接一列存储的。这是Fortran和MATLAB的默认方式。order参数在很多数组创建和操作函数如np.zeros,np.ones,np.array,reshape中都存在。# 创建一个2x3的数组观察其扁平化后的顺序 arr_c np.array([[1, 2, 3], [4, 5, 6]], orderC) print(C-order array flattened:, arr_c.ravel()) # 输出[1 2 3 4 5 6] arr_f np.array([[1, 2, 3], [4, 5, 6]], orderF) print(F-order array flattened:, arr_f.ravel()) # 输出[1 4 2 5 3 6]为什么需要关心这个性能如果你的算法主要按行遍历数组那么使用C顺序的数组会更快因为CPU缓存预取机制能更好地工作访问的内存地址是连续的。反之按列遍历则F顺序更快。与其他库交互例如如果你要将数据传递给一个用Fortran写的数值计算库或者从MATLAB保存的.mat文件中加载数据可能需要指定或转换顺序。重塑reshape操作当使用reshape函数时默认使用orderC这意味着它会按照C顺序行优先读取元素并填充到新形状中。如果你有一个F顺序的数组并用C顺序去重塑可能会得到意想不到的结果。arr np.arange(6) # [0 1 2 3 4 5] reshaped_c arr.reshape((2, 3), orderC) print(Reshape with C-order:) print(reshaped_c) # 输出 # [[0 1 2] # [3 4 5]] reshaped_f arr.reshape((2, 3), orderF) print(Reshape with F-order:) print(reshaped_f) # 输出 # [[0 2 4] # [1 3 5]]我的经验在绝大多数Python和Numpy生态的场景下使用默认的orderC即可。除非你有明确的性能分析证据表明列优先遍历是瓶颈或者需要与特定库交互否则不必特意修改。但了解这个概念在调试一些诡异的数组重塑或切片问题时能帮你快速定位原因。6. 从文件与真实数据源创建数组在实际项目中数据很少是手动生成的更多来源于文件。Numpy提供了高效的文件读写功能。6.1 文本文件加载np.loadtxt与np.genfromtxt对于格式规整的文本文件如CSV空格分隔这两个函数是首选。np.loadtxt更简单、更快适用于“干净”的数据。# 假设 data.csv 内容 # 1.0, 2.0, 3.0 # 4.0, 5.0, 6.0 data_from_txt np.loadtxt(data.csv, delimiter,) print(data_from_txt) # 输出 # [[1. 2. 3.] # [4. 5. 6.]] # 可以跳过行头指定列设置数据类型 data np.loadtxt(data.csv, delimiter,, skiprows1, usecols(0, 2), dtypenp.float32)np.genfromtxt功能更强大可以处理缺失值、不同数据类型列等“脏”数据。# 假设 data_missing.csv 内容 # name, age, score # Alice, 25, 95.5 # Bob, , 88.0 (年龄缺失) # Charlie, 30, data_gen np.genfromtxt(data_missing.csv, delimiter,, skip_header1, dtypeNone, namesTrue, encodingutf-8, missing_values, filling_valuesnp.nan) # dtypeNone: 自动推断每列类型 # namesTrue: 第一行作为字段名返回一个结构化数组structured array # 此时 data_gen 类似于一个字典数组可以通过 data_gen[age] 访问6.2 二进制文件保存与加载np.save与np.load这是Numpy自有的高效二进制格式保存和加载速度极快且能完美保留数组的dtype、shape等信息。# 保存单个数组到 .npy 文件 arr_to_save np.random.randn(1000, 1000) np.save(large_array.npy, arr_to_save) # 文件扩展名通常为 .npy # 加载 .npy 文件 arr_loaded np.load(large_array.npy) print(arr_loaded.shape, arr_loaded.dtype) # 完全恢复 # 保存多个数组到一个 .npz 文件压缩格式 arr1 np.ones((5, 5)) arr2 np.zeros((3, 3)) np.savez(multiple_arrays.npz, matrix_onesarr1, matrix_zerosarr2) # 加载 .npz 文件类似字典 loaded_archive np.load(multiple_arrays.npz) print(loaded_archive.files) # 输出[matrix_ones, matrix_zeros] arr1_loaded loaded_archive[matrix_ones]强烈建议在数据处理流水线的中间步骤如果需要暂存大型中间结果使用.npy或.npz格式。它比保存为文本文件如CSV要快几个数量级也更节省磁盘空间。6.3 从Pandas DataFrame转换Pandas是另一个数据分析的核心库其DataFrame可以看作是带有标签的、更灵活的二维表格。它们之间的转换非常频繁且自然。import pandas as pd # 创建一个简单的DataFrame df pd.DataFrame({A: [1, 2, 3], B: [4.5, 5.5, 6.5], C: [x, y, z]}) print(df) # 将DataFrame的数值部分转换为Numpy数组 # .values 属性返回的是Numpy数组在旧版本中 # .to_numpy() 是现在推荐的方法它总是返回一个Numpy数组 arr_from_df df[[A, B]].to_numpy() # 只选择数值列 print(arr_from_df) print(arr_from_df.dtype) # 输出float64。因为B列是浮点A列被自动提升。 # 注意如果DataFrame包含非数值类型如字符串列‘C’直接to_numpy()会导致dtypeobject arr_object df.to_numpy() print(arr_object.dtype) # 输出object从DataFrame转换时务必注意数据类型。混合类型列会被转换为object类型的Numpy数组这会丧失Numpy的数值计算性能优势。通常我们只提取需要的数值列进行转换。7. 综合案例一个图像处理管道的数组创建实战让我们用一个接近真实场景的例子串联起多种数组创建方法。假设我们要模拟一个简单的图像处理流程生成一张合成图像然后添加噪声。import numpy as np import matplotlib.pyplot as plt # 用于可视化非Numpy核心但很常用 # 1. 创建一张“干净”的灰度测试图像512x512 height, width 512, 512 # 使用 mgrid 生成网格坐标中心在图像中央 Y, X np.mgrid[0:height, 0:width] center_y, center_x height // 2, width // 2 # 2. 创建一个径向渐变图像距离中心越远值越大 # 计算每个像素到中心的欧氏距离 radius np.sqrt((X - center_x)**2 (Y - center_y)**2) # 将距离归一化到0-1范围 max_radius np.sqrt(center_x**2 center_y**2) gradient radius / max_radius # gradient 现在是一个512x512的浮点数组值在[0, 1] # 3. 创建一个正弦波纹理图像 frequency 10 texture 0.5 * (1 np.sin(2 * np.pi * frequency * X / width)) # texture 也是一个512x512的数组 # 4. 合成最终“干净”图像70%渐变 30%纹理 clean_image 0.7 * gradient 0.3 * texture # 确保值在[0,1]之间方便后续当作图像处理 clean_image np.clip(clean_image, 0, 1) # 5. 添加高斯噪声模拟真实传感器噪声 noise_intensity 0.1 # 使用新的RNG API生成噪声 rng np.random.default_rng(seed123) gaussian_noise rng.normal(loc0.0, scalenoise_intensity, size(height, width)) noisy_image clean_image gaussian_noise noisy_image np.clip(noisy_image, 0, 1) # 再次裁剪到有效范围 # 6. 将浮点图像数组转换为8位无符号整数图像数组0-255这是常见的图像存储格式 # 注意转换会量化丢失一些精度 image_uint8 (noisy_image * 255).astype(np.uint8) print(干净图像数组信息:, clean_image.shape, clean_image.dtype, clean_image.min(), clean_image.max()) print(噪声图像数组信息:, noisy_image.shape, noisy_image.dtype, noisy_image.min(), noisy_image.max()) print(8位图像数组信息:, image_uint8.shape, image_uint8.dtype, image_uint8.min(), image_uint8.max()) # 7. 可视化可选 fig, axes plt.subplots(1, 3, figsize(12, 4)) axes[0].imshow(clean_image, cmapgray) axes[0].set_title(Clean Image (Float)) axes[0].axis(off) axes[1].imshow(noisy_image, cmapgray) axes[1].set_title(Noisy Image (Float)) axes[1].axis(off) axes[2].imshow(image_uint8, cmapgray) axes[2].set_title(Noisy Image (Uint8)) axes[2].axis(off) plt.tight_layout() plt.show()这个案例中我们先后使用了np.mgrid生成坐标网格。基本的数组运算加减乘除、平方、开方创建了gradient和texture数组。注意这些运算都是向量化的没有用到任何循环。np.clip函数来限制数组值范围虽然不是创建函数但常用于数据预处理。np.random.default_rng().normal()创建符合特定分布的随机噪声数组。.astype(np.uint8)进行数组类型转换这本质上也是创建了一个新的数组。整个过程完全在Numpy数组层面操作高效且简洁。这就是为什么说熟练创建和操作Numpy数组是进行高效数值计算和数据处理的基础。当你拿到一个任务第一步就是思考我需要什么样的数组用什么方法创建最合适想清楚了这一点代码就成功了一半。