资讯详情

资讯详情

HCIA-AI题库源码化:用Python+Jupyter实现可运行验证与自动化测试

简介这份资源是华为HCIA-AI认证V4.031升级考试的题库整理包面向准备考取华为人工智能基础认证的考生与AI入门学习者作者以950多分通过考试后将刷题经验与题目答案系统汇总帮助读者高效复习、查漏补缺。压缩包共3个文件以html题库页面为主辅以inscode配置与gitignore文件整体约13KB轻量易打开可直接在浏览器中浏览题目与答案。题库覆盖昇腾AI处理器、Transformer优势、大模型部署、LLM训练挑战、智能驾驶、卷积神经网络、反向传播、知识蒸馏、MindSpore框架与梯度下降等核心考点每道题均附正确答案既贴合考试大纲也呼应行业前沿应用。目前已有127人学习下载适合希望系统梳理AI基础知识、提升认证通过率的备考人员参考使用。1. 从 HCIA-AI 备考说起为什么我把题库做成了可运行源码很多人准备 HCIA-AI 认证时第一反应是找一份 PDF 题库把答案背下来。我最初也是这么干的结果第一次模拟测试就翻车了——题目问的是某个激活函数在特定输入区间的输出特性我背过答案但题目换了个参数问法我完全不知道从哪推。那次之后我意识到HCIA-AI 的题库如果只是静态文本你记住的是答案不是知识。真正有效的备考方式是把每道题背后的计算过程、模型结构、评估指标都跑一遍让代码告诉你为什么选 A 不选 B。这就是我把题库做成可运行源码的出发点。所谓「可运行源码」不是把题目和答案塞进一个 Python 文件里打印出来而是针对题库中涉及计算、推理、数据处理、模型评估的题目写一段能独立执行、能改参数、能看中间结果的代码。你运行一次比背十遍答案管用。这篇文章面向的是正在备考 HCIA-AI、或者已经考过但想系统梳理 AI 基础知识体系的从业者。我会把题库里高频出现的几类题型拆开给出可复现的代码框架、参数设置逻辑以及我在整理过程中踩过的坑。新手可以跟着代码一步步跑熟手可以直接看参数边界和避坑部分。2. 题库源码化从静态题目到可执行验证的完整路径2.1 为什么选择 Python Jupyter 作为题库载体HCIA-AI 的考纲覆盖机器学习基础、深度学习基础、计算机视觉、自然语言处理、华为 AI 平台应用等模块。题库里大量题目涉及数值计算、张量操作、模型评估指标计算这些用 Python 表达最自然。我选择 Jupyter Notebook 作为主要载体原因是它能把题目描述、代码、运行结果、可视化图表放在同一个单元格序列里复习时从上往下执行一遍相当于把整个知识链路重新走了一次。具体来说每个题目对应一个 Notebook 单元格块结构是题目原文Markdown 单元格→ 解题代码Code 单元格→ 运行输出自动保存→ 关键参数注释Markdown 单元格。这样你打开一个 Notebook看到的不只是答案而是完整的推导过程。对于纯概念题比如「以下哪个不是监督学习算法」我会在代码里用 sklearn 快速构造一个分类任务把选项里的算法都跑一遍用实际输出说明为什么某个选项不属于监督学习。环境依赖方面我固定使用 Python 3.9 NumPy 1.24 scikit-learn 1.3 PyTorch 2.0CPU 版本即可。不依赖 GPU因为题库里的计算规模都很小CPU 足够。这样做的好处是你在任何一台普通笔记本上都能复现不需要额外配置 CUDA 环境。2.2 题库目录结构与题目分类映射我把题库按考纲模块分成五个一级目录每个目录下再按题型细分。目录结构如下hcia-ai-question-bank/ ├── 01_ml_basics/ # 机器学习基础 │ ├── linear_regression.ipynb │ ├── logistic_regression.ipynb │ ├── decision_tree.ipynb │ └── evaluation_metrics.ipynb ├── 02_dl_basics/ # 深度学习基础 │ ├── activation_functions.ipynb │ ├── backpropagation.ipynb │ ├── optimizer_comparison.ipynb │ └── regularization.ipynb ├── 03_cv/ # 计算机视觉 │ ├── convolution_demo.ipynb │ ├── pooling_demo.ipynb │ └── image_augmentation.ipynb ├── 04_nlp/ # 自然语言处理 │ ├── tokenization.ipynb │ ├── word_embedding.ipynb │ └── attention_demo.ipynb └── 05_platform/ # AI 平台应用模拟 ├── dataset_management.ipynb └── model_deployment_sim.ipynb每个 Notebook 文件对应题库中一个知识点的所有相关题目。比如activation_functions.ipynb里会集中处理 Sigmoid、Tanh、ReLU、Leaky ReLU 等激活函数的题目包括它们的导数计算、输出范围、优缺点对比。这样分类的好处是你复习一个知识点时能一次性看到所有相关题目的变体而不是在不同页面之间跳来跳去。2.3 把一道选择题变成可运行验证以激活函数为例题库里有一道典型题目「以下关于 ReLU 激活函数的说法哪项是正确的」选项涉及输出范围、梯度消失、计算复杂度等。如果只背答案你记住的是「ReLU 在负半轴梯度为 0可能导致神经元死亡」。但如果你跑一遍代码你会真正理解这个现象。我写的验证代码如下import numpy as np import matplotlib.pyplot as plt # 定义 ReLU 及其导数 def relu(x): return np.maximum(0, x) def relu_derivative(x): return np.where(x 0, 1.0, 0.0) # 生成输入数据覆盖负半轴、零点和正半轴 x np.linspace(-5, 5, 100) y relu(x) dy relu_derivative(x) # 打印关键点的值和梯度 print(x -2.0, ReLU输出 , relu(-2.0), , 梯度 , relu_derivative(-2.0)) print(x 0.0, ReLU输出 , relu(0.0), , 梯度 , relu_derivative(0.0)) print(x 3.0, ReLU输出 , relu(3.0), , 梯度 , relu_derivative(3.0)) # 可视化 plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) plt.plot(x, y, labelReLU) plt.title(ReLU Function) plt.grid(True) plt.subplot(1, 2, 2) plt.plot(x, dy, labelReLU Derivative, colororange) plt.title(ReLU Derivative) plt.grid(True) plt.show()这段代码的逻辑说明relu函数用np.maximum(0, x)实现这是最直接的表达。relu_derivative用np.where(x 0, 1.0, 0.0)实现注意在 x0 处我定义为 0这是工程上的常见处理因为严格来说 ReLU 在 0 点不可导但实际训练中我们取次梯度。参数方面np.linspace(-5, 5, 100)生成 100 个等间距点覆盖了负半轴、零点和正半轴这样你能清楚看到负半轴输出恒为 0、梯度恒为 0 的现象。运行后打印的三行输出直接对应题目选项里的关键判断点。如果你把这段代码跑一遍再回头看题目选项你会发现你不需要背「ReLU 可能导致神经元死亡」这句话因为你亲眼看到了负半轴梯度为 0这意味着一旦某个神经元的权重更新到使输出恒为负它的梯度就永远是 0再也无法恢复。这个理解比背答案深得多。2.4 参数化设计让一道题覆盖多个变体题库里很多题目是同一个知识点的不同参数问法。比如 Sigmoid 函数有的题问输出范围有的题问梯度最大值有的题问与 Tanh 的关系。如果每道题都写一段独立代码维护成本太高。我的做法是写一个参数化的验证函数通过改变参数来覆盖不同变体。import numpy as np def sigmoid(x): return 1 / (1 np.exp(-x)) def sigmoid_derivative(x): s sigmoid(x) return s * (1 - s) # 参数化验证输入范围、采样点数、是否打印极值 def verify_sigmoid(x_min-10, x_max10, num_points1000, print_extremesTrue): x np.linspace(x_min, x_max, num_points) y sigmoid(x) dy sigmoid_derivative(x) if print_extremes: print(f输入范围 [{x_min}, {x_max}]) print(fSigmoid 输出范围: [{y.min():.6f}, {y.max():.6f}]) print(f梯度最大值: {dy.max():.6f} (出现在 x {x[np.argmax(dy)]:.4f})) print(f梯度最小值: {dy.min():.6f}) return x, y, dy # 覆盖题库中的三个变体 verify_sigmoid(-10, 10, 1000, True) # 标准范围 verify_sigmoid(-1, 1, 100, True) # 小范围观察线性近似 verify_sigmoid(-100, 100, 10000, True) # 大范围观察饱和现象逻辑说明verify_sigmoid函数把输入范围、采样点数、是否打印极值作为参数暴露出来。题库里问「Sigmoid 输出范围是多少」你运行第一组参数看到输出范围是[0.000045, 0.999955]近似(0, 1)。问「Sigmoid 梯度最大值是多少」你看到梯度最大值是0.25出现在x0附近。问「为什么 Sigmoid 会导致梯度消失」你运行第三组参数看到当输入范围扩大到[-100, 100]时梯度在大部分区域都接近 0只有x0附近有非零值。这样一道参数化函数就覆盖了题库里所有 Sigmoid 相关题目。参数选择上num_points建议至少 1000否则极值点可能被跳过。x_min和x_max根据题目问法调整问输出范围用[-10, 10]问饱和现象用[-100, 100]问线性近似用[-1, 1]。这些参数边界是我在整理题库时反复试出来的你可以直接抄。3. 高频题型拆解计算题、概念题、代码题的源码化策略3.1 计算题把公式变成可验证的数值实验HCIA-AI 题库里计算题占比很高典型的有卷积输出尺寸计算、池化输出尺寸计算、全连接层参数量计算、准确率/精确率/召回率/F1 计算、交叉熵损失计算。这些题如果只背公式遇到变体就容易出错。我的策略是每个公式写一个独立函数输入参数输出结果并在函数里加注释说明公式来源。以卷积输出尺寸计算为例公式是输出尺寸 (输入尺寸 - 卷积核尺寸 2 * padding) / stride 1。我写的验证函数如下def conv_output_size(input_size, kernel_size, padding0, stride1): 计算卷积层输出尺寸 input_size: 输入特征图尺寸假设宽高相等 kernel_size: 卷积核尺寸 padding: 填充像素数 stride: 步长 output_size (input_size - kernel_size 2 * padding) / stride 1 # 检查是否能整除不能整除时实际输出会向下取整 if output_size ! int(output_size): print(f警告计算结果 {output_size} 不是整数实际输出尺寸为 {int(output_size)}) return int(output_size) # 题库中的典型参数组合 print(conv_output_size(32, 5, 0, 1)) # 输出 28 print(conv_output_size(32, 3, 1, 1)) # 输出 32 print(conv_output_size(224, 7, 3, 2)) # 输出 112 print(conv_output_size(64, 3, 0, 2)) # 输出 31注意不能整除逻辑说明函数直接实现公式但加了一个整除检查。题库里有些题目故意给出不能整除的参数组合问实际输出尺寸是多少。这时候如果你只背公式可能会写 31.5但实际卷积操作中输出尺寸是向下取整的所以是 31。这个细节我在整理时踩过坑后来在函数里加了警告输出提醒自己注意。参数说明input_size和kernel_size是必填项padding默认 0stride默认 1。题库里常见的参数组合我都列在注释里了你可以直接改参数验证。对于池化层公式类似但通常没有 padding且 stride 等于 kernel_size 的情况居多你可以把conv_output_size改个名字复用。3.2 概念题用对比实验代替文字辨析概念题是题库里最容易让人纠结的部分比如「以下哪个不是监督学习」「Batch Normalization 和 Layer Normalization 的区别」「L1 和 L2 正则化的区别」。这些题如果只背文字描述很容易混淆。我的做法是写对比实验用代码输出差异。以 L1 和 L2 正则化为例我写了一个简单的线性回归实验分别加 L1 和 L2 惩罚项观察权重分布import numpy as np from sklearn.linear_model import Lasso, Ridge from sklearn.datasets import make_regression # 生成模拟数据100 个样本50 个特征其中只有 10 个是真正相关的 X, y make_regression(n_samples100, n_features50, n_informative10, noise0.1, random_state42) # L1 正则化Lasso lasso Lasso(alpha0.1) lasso.fit(X, y) l1_nonzero np.sum(lasso.coef_ ! 0) # L2 正则化Ridge ridge Ridge(alpha0.1) ridge.fit(X, y) l2_nonzero np.sum(ridge.coef_ ! 0) print(fL1 正则化后非零权重数量: {l1_nonzero}) print(fL2 正则化后非零权重数量: {l2_nonzero}) print(fL1 权重绝对值之和: {np.sum(np.abs(lasso.coef_)):.4f}) print(fL2 权重平方和: {np.sum(ridge.coef_ ** 2):.4f})逻辑说明make_regression生成的数据里只有 10 个特征真正影响目标值其余 40 个是噪声特征。L1 正则化倾向于把不相关特征的权重压缩到 0所以l1_nonzero应该接近 10。L2 正则化只是把权重整体缩小不会精确到 0所以l2_nonzero会接近 50。运行这段代码你就能直观理解「L1 产生稀疏解L2 产生平滑解」这句话的含义。参数说明alpha是正则化强度值越大惩罚越重。题库里如果问「alpha 增大时权重如何变化」你可以把alpha改成 0.01、0.1、1.0、10.0 分别跑一遍观察非零权重数量的变化趋势。n_informative控制真正相关特征的数量你可以改成 5 或 20观察 L1 是否还能准确筛选出相关特征。3.3 代码题补全代码与调试的标准化流程题库里有一类题目是给出不完整的代码要求补全某个函数或某一行。这类题如果只看答案你很难理解为什么填这一行。我的做法是把不完整代码和完整代码都保存下来用 diff 对比并在完整代码里加注释说明每一行的作用。以「补全一个简单的全连接层前向传播」为例题库给出的不完整代码是def forward(self, x): # 请补全以下代码 pass我补全后的代码如下import numpy as np class SimpleLinearLayer: def __init__(self, input_dim, output_dim): # 权重初始化使用 Xavier 初始化适合 Sigmoid/Tanh self.W np.random.randn(input_dim, output_dim) * np.sqrt(1.0 / input_dim) self.b np.zeros((1, output_dim)) def forward(self, x): # 前向传播y xW b # x 形状 (batch_size, input_dim) # W 形状 (input_dim, output_dim) # b 形状 (1, output_dim)广播到 (batch_size, output_dim) self.x x # 保存输入用于反向传播 self.z np.dot(x, self.W) self.b return self.z # 测试 layer SimpleLinearLayer(4, 3) x np.array([[1, 2, 3, 4], [5, 6, 7, 8]]) output layer.forward(x) print(输入形状:, x.shape) print(输出形状:, output.shape) print(输出值:\n, output)逻辑说明forward函数的核心是np.dot(x, self.W) self.b。这里的关键是理解矩阵乘法的维度匹配x是(batch_size, input_dim)W是(input_dim, output_dim)相乘后得到(batch_size, output_dim)再加上b的广播。题库里有些题目会问「如果输入维度是 4输出维度是 3权重矩阵的形状是什么」你运行这段代码打印self.W.shape看到是(4, 3)答案就明确了。参数说明input_dim和output_dim根据题目给定。权重初始化我用了 Xavier 初始化这是题库里常考的知识点。如果你把初始化改成np.random.randn(input_dim, output_dim)不加缩放你会发现输出值的方差会随着input_dim增大而增大这解释了为什么需要 Xavier 初始化。这个对比实验能帮你理解初始化的重要性。4. 避坑与排查题库源码化过程中最容易翻车的五个点4.1 环境版本不一致导致代码跑不通现象你从别人那里拿到一份 Notebook在自己机器上运行时报AttributeError或ImportError比如np.float在 NumPy 1.24 里已经被移除但旧代码里还在用。原因题库源码化过程中不同人用的库版本不同。NumPy、scikit-learn、PyTorch 的 API 在不同版本之间有破坏性变更。比如np.float在 NumPy 1.20 被弃用1.24 被移除sklearn.cross_validation在 0.20 被移除改成sklearn.model_selection。解决在项目根目录放一个requirements.txt固定版本号。我用的版本是numpy1.24.3、scikit-learn1.3.0、torch2.0.1。如果你拿到别人的代码跑不通先检查版本用pip install -r requirements.txt重装。另外在 Notebook 第一个单元格里加一段版本检查代码import numpy as np import sklearn import torch print(NumPy:, np.__version__) print(scikit-learn:, sklearn.__version__) print(PyTorch:, torch.__version__)这样你一眼就能看出环境是否匹配。4.2 随机种子未固定导致结果不可复现现象你运行代码得到准确率 0.85别人运行同样的代码得到 0.82题目答案对不上。原因机器学习代码里涉及随机初始化、数据划分、Dropout 等随机操作。如果不固定随机种子每次运行结果都不同。解决在代码开头固定所有随机种子。我一般写一个set_seed函数import numpy as np import random import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False set_seed(42)注意torch.backends.cudnn.deterministic True会降低训练速度但能保证结果可复现。题库里的计算题不涉及大规模训练所以这个代价可以接受。如果你在 GPU 上跑还要设置torch.cuda.manual_seed_all。固定种子后你和别人的运行结果应该完全一致。4.3 数据形状不匹配导致广播错误现象运行代码时报ValueError: operands could not be broadcast together with shapes (100, 10) (100,)。原因NumPy 的广播机制虽然方便但也容易让人忽略形状匹配。题库里很多计算题涉及矩阵乘法、加法、拼接形状不匹配时要么报错要么得到意想不到的结果。解决在关键操作前后打印形状。我习惯在函数里加print或assertdef safe_dot(a, b): print(fa.shape {a.shape}, b.shape {b.shape}) assert a.shape[-1] b.shape[0], f维度不匹配: {a.shape[-1]} ! {b.shape[0]} return np.dot(a, b)对于加法注意广播规则(100, 10)和(100,)相加时(100,)会被广播成(1, 100)然后尝试与(100, 10)匹配失败。正确的做法是把(100,)reshape 成(100, 1)或(1, 100)具体取决于你想加在哪一维。题库里有一道题专门考广播我踩过这个坑后来养成了打印形状的习惯。4.4 忽略数值稳定性导致溢出或下溢现象计算 Sigmoid 或 Softmax 时输入值稍大就得到inf或nan。原因np.exp(1000)会溢出np.exp(-1000)会下溢到 0。Sigmoid 函数1 / (1 np.exp(-x))在 x 很大时np.exp(-x)接近 0没问题但 x 很负时np.exp(-x)会溢出。Softmax 函数np.exp(x) / np.sum(np.exp(x))在 x 很大时分子分母都溢出。解决使用数值稳定版本。Sigmoid 可以写成def stable_sigmoid(x): # 对正负分别处理避免 exp 溢出 positive_mask x 0 negative_mask ~positive_mask result np.zeros_like(x, dtypefloat) result[positive_mask] 1 / (1 np.exp(-x[positive_mask])) result[negative_mask] np.exp(x[negative_mask]) / (1 np.exp(x[negative_mask])) return resultSoftmax 的标准稳定写法是减去最大值def stable_softmax(x): # x 形状 (batch_size, num_classes) x_shifted x - np.max(x, axis1, keepdimsTrue) exp_x np.exp(x_shifted) return exp_x / np.sum(exp_x, axis1, keepdimsTrue)题库里有一道题问「为什么 Softmax 要减去最大值」你运行稳定版本和不稳定版本输入[1000, 1001, 1002]不稳定版本会得到nan稳定版本得到正常概率分布。这个对比能帮你记住数值稳定性的重要性。4.5 混淆评估指标的计算口径现象题目问精确率你算出来和答案不一样检查公式发现没错但就是不对。原因精确率、召回率、F1 的计算依赖 TP、FP、FN 的定义而不同题目对「正类」的定义可能不同。有的题目把类别 1 当正类有的把类别 0 当正类。另外多分类问题中精确率有 macro、micro、weighted 三种平均方式题目如果不明确很容易算错。解决在代码里显式定义正类并打印混淆矩阵。我一般用 sklearn 的confusion_matrix和classification_reportfrom sklearn.metrics import confusion_matrix, classification_report import numpy as np y_true np.array([0, 1, 1, 0, 1, 0, 1, 1, 0, 0]) y_pred np.array([0, 1, 0, 0, 1, 0, 1, 0, 0, 1]) # 显式指定正类为 1 cm confusion_matrix(y_true, y_pred, labels[0, 1]) print(混淆矩阵:\n, cm) print(\n分类报告:\n, classification_report(y_true, y_pred, labels[0, 1])) # 手动计算精确率和召回率验证口径 TP np.sum((y_true 1) (y_pred 1)) FP np.sum((y_true 0) (y_pred 1)) FN np.sum((y_true 1) (y_pred 0)) precision TP / (TP FP) if (TP FP) 0 else 0 recall TP / (TP FN) if (TP FN) 0 else 0 print(f\n手动计算: 精确率{precision:.4f}, 召回率{recall:.4f})逻辑说明confusion_matrix的labels参数显式指定了类别顺序这样你就能清楚看到 TP、FP、FN 分别是多少。classification_report会输出精确率、召回率、F1你可以和手动计算的结果对比。如果题目问的是 macro 平均你需要在classification_report里加averagemacro。这个坑我在整理题库时踩过好几次后来养成了先打印混淆矩阵再算指标的习惯。5. 进阶技巧用 pytest 给题库做自动化回归测试5.1 为什么题库也需要单元测试题库源码化之后你会有几十个 Notebook 和 Python 文件。每次你修改某个函数的参数或者升级库版本都可能影响其他题目的运行结果。如果没有测试你很难发现哪些题目被改坏了。我给题库加了一层 pytest 测试把每道计算题的预期输出写成断言这样每次修改后跑一遍pytest就能快速定位问题。测试文件放在tests/目录下命名规则是test_模块名.py。比如test_conv_output_size.py对应卷积输出尺寸计算。5.2 用参数化测试覆盖题库变体pytest 的pytest.mark.parametrize非常适合题库场景因为同一道题有多个参数变体。我写的测试如下import pytest from hcia_ai_question_bank.cv.conv_utils import conv_output_size pytest.mark.parametrize(input_size, kernel_size, padding, stride, expected, [ (32, 5, 0, 1, 28), # 标准卷积无填充 (32, 3, 1, 1, 32), # same padding (224, 7, 3, 2, 112), # 典型 ResNet 第一层 (64, 3, 0, 2, 31), # 不能整除向下取整 (28, 5, 2, 1, 28), # 填充使输出与输入相同 ]) def test_conv_output_size(input_size, kernel_size, padding, stride, expected): result conv_output_size(input_size, kernel_size, padding, stride) assert result expected, f输入 {input_size}, 核 {kernel_size}, 填充 {padding}, 步长 {stride}: 期望 {expected}, 实际 {result}逻辑说明parametrize装饰器把五组参数注入测试函数每组参数对应题库里的一道题。expected是我根据公式和实际运行结果确定的预期值。如果某次修改导致某个参数组合的输出变了pytest 会明确指出是哪一组失败。这样你不需要手动打开每个 Notebook 检查跑一遍测试就知道题库是否还健康。参数说明input_size、kernel_size、padding、stride对应卷积层的四个超参数。expected是预期输出尺寸。你可以继续往列表里加参数组合覆盖题库里所有卷积相关题目。对于池化层你可以写一个类似的test_pool_output_size参数组合不同但逻辑相同。5.3 把测试结果变成复习清单pytest 运行后会输出一份报告告诉你哪些测试通过、哪些失败。我把这份报告当成复习清单通过的题目说明你已经掌握失败的题目说明你还没理解透。每次复习前跑一遍pytest -v失败的题目就是你需要重点看的。另外我还会在测试文件里加一个test_all_notebooks_run用nbconvert执行所有 Notebook确保它们都能从头跑到尾不报错import subprocess import glob def test_all_notebooks_run(): notebooks glob.glob(hcia_ai_question_bank/**/*.ipynb, recursiveTrue) for nb in notebooks: result subprocess.run( [jupyter, nbconvert, --to, notebook, --execute, --inplace, nb], capture_outputTrue, textTrue ) assert result.returncode 0, fNotebook {nb} 执行失败:\n{result.stderr}这个测试会遍历所有 Notebook逐个执行。如果某个 Notebook 因为库版本升级或代码修改而报错你会第一时间发现。注意--inplace会覆盖原文件建议先备份或者改成--output输出到临时目录。5.4 我踩过的测试坑与最终习惯第一个坑是测试文件里硬编码了绝对路径换一台机器就找不到文件。后来我改用pathlib.Path(__file__).parent来定位相对路径。第二个坑是 Notebook 执行测试太慢几十个 Notebook 跑一遍要几分钟。后来我加了pytest.mark.slow标记日常只跑快速测试发版前才跑全量。第三个坑是断言里用了浮点数直接比较比如assert result 0.25结果因为浮点精度问题失败。后来我改成assert abs(result - 0.25) 1e-6。现在我的习惯是每整理完一个模块的题目就同步写对应的测试文件每次修改公共函数先跑测试再提交每次复习前先跑一遍全量测试失败的题目优先看。这套流程让我在备考后期节省了大量重复检查的时间也让我对题库里每个知识点的边界条件有了更清晰的认识。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →