上一节,我们把电催化信息组织成了"列表中的字典"。现在,程序已经能够找到一条记录的电流、面积、电位与来源。
但如果某条记录的面积为零、电流没有填写,或者参考电极尚未确认,程序应该怎样处理?
本节学习如何把科研中的检查要求写成明确规则,并让程序逐条执行。
一、本节学习目标
完成本节后,你应能够:
- ++理解条件判断产生的`True`与`False`。++
- ++使用`if`、`elif`和`else`选择执行步骤。++
- ++使用`for`循环逐条处理记录。++
- ++区分缺字段、未知值、空白文字与数值零。++
- ++在计算前检查数据,并保存问题原因。++
- ++保留原始记录,区分"可以计算"和"科学可信"。++
- ++根据实际证据总结前五节的学习情况。++
本节使用Python标准库,不定义函数,不读取CSV,也不训练机器学习模型。函数与文件读写分别安排在第六、七节。
二、为什么不能直接对所有记录计算?
电流密度的基本关系是:
\ j_{\\mathrm{geo}}=\\frac{I}{A_{\\mathrm{geo}}} \\
其中,\(I\)是电流,\(A_{\mathrm{geo}}\)是几何面积,\(j_{\mathrm{geo}}\)是按几何面积计算的平均电流密度。
如果电流为−12.5 mA、面积为0.50 cm²,则:
\ j_{\\mathrm{geo}}=-25\\ \\mathrm{mA/cm\^2} \\
但遇到下面的记录,就不能直接相除:
record = {
"current_mA": None,
"area_cm2": 0.0,
}
电流未知,面积又是零。即使只修复其中一个问题,另一个问题仍然存在。
因此,一套基础处理流程应当是:
读取记录
→ 检查字段与数值
→ 记录问题
→ 判断是否满足本次计算要求
→ 计算或暂缓
→ 保存处理结果
这里的"暂缓"表示保留记录并等待核实,不等于删除数据。
三、条件判断:把检查要求变成真假问题
假设:
area_cm2 = 0.50
表达式:
area_cm2 > 0
提出的问题是:"面积是否大于零?"
结果为:
True
如果面积为0,则结果为:
False
True与`False`是布尔值,类型名为`bool`,分别表示真与假。它们不需要引号。
True
与:
"True"
不同:前者是布尔值,后者是字符串。
常用比较符号如下:
| 符号 | 含义 | 示例 |
|---|---|---|
> |
大于 | area_cm2 > 0 |
< |
小于 | current_mA < 0 |
>= |
大于等于 | temperature_K >= 273.15 |
<= |
小于等于 | area_cm2 <= 0 |
== |
比较相等 | reference == "RHE" |
!= |
比较不相等 | reference != "RHE" |
需要区分:
area_cm2 = 0.50
是赋值,而:
area_cm2 == 0.50
是比较。
检查规则还必须符合物理意义。负电流可能符合阴极电流的符号约定,不能一律当作错误;面积作为本课归一化分母,则应为正。
四、用if和else决定执行什么
先看一个完整小例子:
area_cm2 = 0.50
if area_cm2 > 0:
print("Area can be used")
else:
print("Check area before division")
执行过程是:
- 判断`area_cm2 > 0`。
- 如果成立,执行第一段输出。
- 否则,执行`else`下面的输出。
冒号后面的缩进表示代码块。这里统一采用四个空格。
缩进决定执行结构,不只是排版。
例如:
if area_cm2 > 0:
print("Positive area")
print("Check completed")
最后一行没有缩进,因此无论面积是否为正,都会执行。
如果希望区分多个情况,可以使用`elif`,意思是"否则,再检查一个条件":
area_cm2 = None
if area_cm2 is None:
print("Missing area")
elif type(area_cm2) not in (int, float):
print("Area must be numeric")
elif area_cm2 <= 0:
print("Area must be positive")
else:
print("Ready for calculation")
这段代码先检查缺失,再检查类型,最后检查数值范围。
这样安排,是为了避免直接对`None`或文字进行大小比较。条件分支与循环的基本行为见Python官方控制流教程。
五、缺失、零和空文字不能混为一谈
下面四种情况不同:
record = {}
这是没有对应字段。
record = {"current_mA": None}
这是字段存在,但明确标记为未知。
record = {"current_mA": ""}
这是空字符串。
record = {"current_mA": 0.0}
这是一个明确的数值零。
零电流可能是真实工作点;零电位也可能具有明确物理含义。它们不能因为数值为零就被当作缺失。
因此,不宜这样检查电流是否缺失:
if not current_mA:
print("Missing current")
因为`0.0`在这种判断中也会被当作假值。
本节使用明确判断:
if current_mA is None:
print("Missing current")
面积为零则不同:它不能作为本课的归一化分母。检查规则取决于字段的物理意义。
同样,未知温度不能直接填入"室温",未知参考电极也不能默认写成RHE。
六、for循环:对每条记录重复执行步骤
上一节需要分别写三条计算语句:
sample_a["j_mA_cm2"] = sample_a["current_mA"] / sample_a["area_cm2"]
sample_b["j_mA_cm2"] = sample_b["current_mA"] / sample_b["area_cm2"]
sample_c["j_mA_cm2"] = sample_c["current_mA"] / sample_c["area_cm2"]
当记录增加时,可以用循环重复执行同一套步骤。
先看最简单的例子:
record_ids = ["A_run01", "B_run01", "C_run01"]
for record_id in record_ids:
print(record_id)
输出:
A_run01
B_run01
C_run01
每一轮,程序从列表中取一个元素,用`record_id`这个名字引用它,再执行缩进块。
对于字典记录,可以写:
for record in records:
print(record["record_id"])
这里的`record`依次代表一条字典。
循环只是重复我们写定的步骤,不会自动学习检查规则,也不等于机器学习。
七、组合条件与检查字段
有些检查需要多个条件配合。
and:两个条件都成立。or:至少一个条件成立。not:对真假结果取反。in:判断是否包含某项。not in:判断是否不包含某项。
例如:
if "current_mA" not in record:
print("Current field is absent")
对字典使用`in`时,检查的是键是否存在。
检查文字是否为空,可以使用:
value.strip() == ""
strip()返回去除首尾空白后的文字。例如,只含空格的字符串经过处理后就是空字符串。
但应先确认`value`是字符串,再调用文字工具:
if type(value) is str and value.strip() != "":
print("Non-empty text")
and从左往右判断;如果前面不成立,就不会继续执行右边。这可以避免对`None`调用字符串操作。
八、数值类型正确,还要检查是否有限
科研数据处理中可能遇到:
NaN:一种"非数值"的特殊浮点表示。inf:无穷大。
它们不适合作为本课的普通测量数值直接计算。
Python标准库提供:
import math
print(math.isfinite(12.5))
输出`True`。如果数值是NaN或无穷大,则返回`False`。官方说明
调用前仍要先确认类型。
本课使用:
type(value) not in (int, float)
其中,`(int, float)`把允许的两种类型组成一个组合,称为元组。本节只需理解它在这里列出允许类型的作用。
这项检查也会挡住`True`和`False`,避免把布尔标记当成1或0的测量值。
九、明确本次数据检查规则
本次练习检查四个数值字段:
numeric_fields = [
"current_mA",
"area_cm2",
"potential_mV",
"temperature_K",
]
它们必须:
- 字段存在。
- 不为`None`。
- 是本课接受的数值类型。
- 是有限数值。
- 面积为正,温度符合本练习的正值要求。
文字字段包括编号、反应、参考、电解液、面积基准、iR状态和来源,要求是非空字符串。
此外:
- 本批记录编号不得重复。
- 本次只处理HER工作点。
- 电位参考为RHE。
- 面积基准为几何面积。
最后三项是本次任务的准入范围,不是通用物理定律。采用其他参考电极或面积基准的数据,可能完全有效,但需要相应处理,不能直接混入当前计算。
这些检查也不能发现全部问题。例如,字段名写`current_mA`,实际却填入以A表示的数值,程序未必能自动识别。非空电解液说明也不证明不同记录条件相同。
十、用七条教学记录检验程序
本节沿用第四节的三条教学工作点:
| 记录 | 电流/mA | 几何面积/cm² | 预期电流密度/(mA/cm²) |
|---|---|---|---|
| A | −12.5 | 0.50 | −25 |
| B | −20.0 | 0.50 | −40 |
| C | −10.0 | 0.25 | −40 |
共同条件为HER、−180 mV vs RHE、0.10 mol/L HClO₄、298.15 K、几何面积归一化、未应用iR补偿。
这些数值全部为教学设定,不是实验测量。
然后加入四条故障测试记录:
| 输入 | 设置的问题 | 预期处理 |
|---|---|---|
| D | 面积为0 | 暂缓计算 |
| E | 电流为`None` | 暂缓计算 |
| F | 参考电极为`None` | 暂缓计算 |
| 重复A | 再次使用A的记录编号 | 保留并标记重复 |
本课用`PASS`表示满足当前规则,用`HOLD`表示保留待核实。
HOLD不等于实验无效,更不等于删除。

十一、完整、可运行的Python代码
下面代码使用第四节学过的平面字典复制来构造教学输入,再进行批量检查。
import math
# All input records are teaching assumptions, not measured data.
sample_a = {
"record_id": "A_run01",
"sample_id": "demo_A",
"reaction": "HER",
"potential_mV": -180.0,
"reference": "RHE",
"electrolyte": "0.10 mol/L HClO4",
"temperature_K": 298.15,
"current_mA": -12.5,
"area_cm2": 0.50,
"area_basis": "geometric",
"ir_compensation": "not applied",
"source": "teaching, not measured",
}
sample_b = sample_a.copy()
sample_b["record_id"] = "B_run01"
sample_b["sample_id"] = "demo_B"
sample_b["current_mA"] = -20.0
sample_c = sample_a.copy()
sample_c["record_id"] = "C_run01"
sample_c["sample_id"] = "demo_C"
sample_c["current_mA"] = -10.0
sample_c["area_cm2"] = 0.25
bad_area = sample_a.copy()
bad_area["record_id"] = "D_run01"
bad_area["area_cm2"] = 0.0
missing_current = sample_a.copy()
missing_current["record_id"] = "E_run01"
missing_current["current_mA"] = None
missing_reference = sample_a.copy()
missing_reference["record_id"] = "F_run01"
missing_reference["reference"] = None
duplicate_id = sample_a.copy()
records = [
sample_a, sample_b, sample_c, bad_area,
missing_current, missing_reference, duplicate_id,
]
numeric_fields = [
"current_mA", "area_cm2", "potential_mV", "temperature_K",
]
text_fields = [
"record_id", "sample_id", "reaction", "reference",
"electrolyte", "area_basis", "ir_compensation", "source",
]
seen_ids = []
accepted = []
audit = []
for record in records:
reasons = []
j_value = None
for key in numeric_fields:
if key not in record:
reasons.append(key + ": absent")
elif record[key] is None:
reasons.append(key + ": missing")
elif type(record[key]) not in (int, float):
reasons.append(key + ": not numeric")
elif not math.isfinite(record[key]):
reasons.append(key + ": not finite")
elif key == "area_cm2" and record[key] <= 0:
reasons.append(key + ": must be positive")
elif key == "temperature_K" and record[key] <= 0:
reasons.append(key + ": must be positive for this experiment")
for key in text_fields:
value = record.get(key)
if type(value) is not str:
reasons.append(key + ": missing or not text")
elif value.strip() == "":
reasons.append(key + ": empty text")
record_id = record.get("record_id")
if type(record_id) is str and record_id.strip() != "":
if record_id in seen_ids:
reasons.append("record_id: duplicate")
else:
seen_ids.append(record_id)
# Eligibility rules for this exercise
if record.get("reaction") != "HER":
reasons.append("reaction: outside this exercise")
if record.get("reference") != "RHE":
reasons.append("reference: unconfirmed or outside this exercise")
if record.get("area_basis") != "geometric":
reasons.append("area_basis: outside this exercise")
if len(reasons) == 0:
j_value = record["current_mA"] / record["area_cm2"]
if not math.isfinite(j_value):
reasons.append("computed j: not finite")
j_value = None
if len(reasons) == 0:
checked_record = record.copy()
checked_record["j_mA_cm2"] = j_value
accepted.append(checked_record)
status = "PASS"
else:
status = "HOLD"
audit.append({
"record_id": record_id,
"status": status,
"reasons": reasons,
"j_mA_cm2": j_value,
})
print(record_id, status, "j =", j_value, "reasons =", reasons)
print("Total records:", len(records))
print("Passed for this exercise:", len(accepted))
print("Held for review:", len(records) - len(accepted))
print("Raw inputs were preserved; PASS is not scientific validation.")
十二、逐段理解代码
代码较长,但可以分成六个清楚的部分。
第一部分构造输入。前三条是可计算教学记录,后四条用于检验错误能否被发现。
第二部分定义字段规则。把要检查的字段名称放入列表,避免逐条重复写检查语句。
第三部分是外层循环:
for record in records:
每一轮处理一条记录。循环内部重新建立:
reasons = []
这样上一条记录的问题不会传到下一条。
第四部分使用内层循环逐个检查字段:
for key in numeric_fields:
数值检查采用"是否存在→是否未知→类型→有限性→范围"的顺序,避免在前提不满足时执行不合适的操作。
第五部分在全部检查通过后才计算。结果写入复制得到的新字典,原输入不增加电流密度字段。
第六部分保存审计信息:
audit.append(...)
audit保存所有记录的状态和原因;`accepted`只保存本次可计算的副本。
seen_ids放在外层循环之外,是因为它需要记住前面见过的编号。如果每轮都清空,就无法发现重复。
本例按输入顺序接纳首次出现的编号,对后续重复编号暂缓。这只是教学处理约定,不证明首条记录一定正确;真实冲突仍须回查来源。
十三、运行结果与数学核对
在项目根目录的PowerShell中运行:
& 'C:\Users\Admin\AppData\Roaming\uv\python\cpython-3.11-windows-x86_64-none\python.exe' '.\01_课程\Day05\code\Day05_checks.py'
本机代码已实际运行,主要输出为:
A_run01 PASS j = -25.0 reasons = []
B_run01 PASS j = -40.0 reasons = []
C_run01 PASS j = -40.0 reasons = []
D_run01 HOLD j = None reasons = ['area_cm2: must be positive']
E_run01 HOLD j = None reasons = ['current_mA: missing']
F_run01 HOLD j = None reasons = ['reference: missing or not text', 'reference: unconfirmed or outside this exercise']
A_run01 HOLD j = None reasons = ['record_id: duplicate']
Total records: 7
Passed for this exercise: 3
Held for review: 4
三个通过记录分别满足:
\ j_A=-12.5/0.50=-25\\ \\mathrm{mA/cm\^2} \\\ j_B=-20.0/0.50=-40\\ \\mathrm{mA/cm\^2} \\\ j_C=-10.0/0.25=-40\\ \\mathrm{mA/cm\^2} \\
反向核对时,\(jA\)应恢复原电流。
计数也应满足:
\ N_{\\mathrm{输入}} = N_{\\mathrm{通过}} + N_{\\mathrm{暂缓}} = 3+4=7 \\
F记录有两个问题原因,但仍然只是一条暂缓记录。不能把原因条数当成暂缓记录数。
同样,3/7只是本教学批次的准入比例,不是测量准确率,也不是模型准确率。
额外边界测试已确认:零电流可以通过;`None`、数字文字、布尔值、NaN和无穷值会被拦截。原始记录保持不变。
十四、真实电催化数据为什么还需要物理审查?
此前课程使用了Nørskov等2005年论文中的Pt记录,其指标是交换电流密度的对数:
\ \\log_{10}\\left\[\\frac{j_0}{1\\ \\mathrm{A/cm\^2}}\\right=-3.1 \]
文献为 Trends in the Exchange Current for Hydrogen Evolution ,发表于 Journal of The Electrochemical Society 。论文DOI
这条真实文献记录不能直接放入本节的工作点\(I/A\)计算流程,因为指标定义不同,而且没有为当前流程核实完整的工作点字段。
正确做法是保留来源、说明缺少什么、决定是否适合当前任务。不能为了让程序通过,就补入教学设定的电位、面积和电解液。
同样,一条记录通过本节格式检查,也不能证明:
- 原始测量真实且无误。
- 单位名称与实际数据一致。
- 不同实验条件已经可比。
- 几何电流密度代表本征活性。
- 数据已经适合训练模型。
本节检查是科研质量控制的一个起点。
十五、练习与验收
基础练习:
- 解释`=`与`==`,以及`True`与`"True"`的区别。
- 对`None`、0、负面积和正常面积,预测程序经过哪些分支。
- 用`for`打印三条记录的编号。
- 解释为什么`reasons`每轮清空,而`seen_ids`不清空。
进阶练习:
- 在副本中把电流改为0,确认记录可以通过且\(j=0\)。
- 分别用数字文字、NaN和`True`替换电流,观察原因。
- 同时缺少电流和参考电极,确认多个问题均被记录。
- 测试缺键、空白来源和不在本次范围内的参考电极。
- 核对原始输入没有新增电流密度字段。
科研思考:
- 发现未知值后,能否直接删除所有未知样本?
- 重复记录编号能否自动取平均?
- 通过本节检查是否意味着可以直接用于机器学习?
- 根据电流大小删除"低活性异常值",可能怎样改变研究结论?
本节按概念25分、数学20分、代码25分、科学解释20分、复现记录10分评分。
达到80分,且能做到先检查后计算、正确区分未知与零、保留原始信息及暂缓原因、不把`PASS`当作科研验证,才算通过本节验收。
十六、第一次五天学习总结
前五节已经建立以下学习路线:
| 单元 | 课程内容 | 需要由学习者独立证明的能力 |
|---|---|---|
| Day 1 | AI基本概念 | 解释概念关系、训练与验证 |
| Day 2 | 文件与运行环境 | 保存脚本并确认实际解释器 |
| Day 3 | 变量与单位 | 正确换算、保留参考与符号 |
| Day 4 | 列表与字典 | 组织带条件、可溯源的记录 |
| Day 5 | 条件与循环检查 | 批量检查、保存问题并核查结果 |
下一节严格按目录学习 "函数、参数、返回值与错误定位"。