第三篇实践基础:编程、数据处理与可视化
第5章 Python与化学数据分析入门
“The greatest value of a picture is when it forces us to notice what we never expected to see.”
“图像最大的价值,在于它迫使我们注意到原本未曾预料的现象。”
——约翰·W. 图基(John W. Tukey)
Python已成为化学信息学、计算化学、材料信息学与实验室自动化中最常用的编程语言之一。本章面向尚无编程基础或仅具备初步基础的化学专业读者,介绍Python的基本语法、常用数据分析工具,以及化学数据文件读取、处理和可视化的基本方法。学习本章后,读者应能够在Jupyter Notebook中完成简单的化学实验数据整理、统计分析和图形绘制,并为后续使用RDKit、scikit-learn等工具开展化学机器学习学习奠定基础。
学习目标
- 说明Python在化学研究中的主要应用场景,理解其在实验数据处理、化学信息学、计算化学结果分析、机器学习与科研可视化中的作用。
- 完成Python基础运行环境的配置并使用Jupyter Notebook,能够以可重复的方式组织代码、文字说明、图形与分析结果。
- 掌握Python的基本语法,包括变量与常用数据类型、列表和字典等数据结构、条件判断、循环及函数的基本用法。
- 使用NumPy和Pandas处理化学表格数据,能够完成数据读取、检查、筛选、分组统计、缺失值与重复值处理以及基本的数据类型转换。
- 使用Matplotlib和Seaborn绘制规范的化学数据图形,包括折线图、散点图、直方图和相关性热图,并正确标注变量名称、单位和图例。
- 理解化学数据预处理的基本原则,能够关注单位统一、异常值核查、实验重复、数据来源与结构标准化等问题。
- 初步读取和处理包含SMILES的分子数据,理解利用RDKit将分子结构转化为可计算描述符的基本流程。
- 对可视化和统计分析结果作出审慎的化学解释,区分统计相关与因果关系,并认识数据规模、实验条件和适用范围对结论的限制。
5.1 Python在化学研究中的应用概览
Python是一种解释型、高层次、通用型编程语言,具有语法简洁、生态系统完善、跨平台运行和社区活跃等特点。所谓“解释型”,是指程序通常可以由解释器逐段执行,便于研究者在交互式环境中即时测试代码、查看中间结果和修正分析思路;所谓“高层次”,则意味着Python用较接近自然语言的表达方式封装了许多底层计算细节,使研究者能够将更多精力投入科学问题本身,而非复杂的程序实现。
与传统科研计算语言相比,Python尤其适合快速编写数据处理脚本、构建可重复分析流程,以及连接不同的软件工具。对于化学工作者而言,Python的价值并不只在于“会写程序”,更在于它能够把原本分散在 Excel 表格、仪器导出文件、量子化学输出文件、分子结构数据库和实验记录本中的信息,组织为可检查、可计算、可视化和可复用的数据资源。
Python已广泛应用于化学信息学、计算化学、材料信息学、药物发现、分析化学、合成化学和实验室自动化等领域。其应用常常贯穿研究工作的多个阶段:从原始数据采集与整理,到结构表示与特征计算;从统计分析和机器学习建模,到结果可视化、实验设计和知识发现。下面介绍其主要应用场景。
5.1.1 化学数据整理与统计分析
现代化学研究产生的数据形式十分多样。例如,合成化学实验中可能记录反应物投料量、溶剂、催化剂、温度、反应时间和分离收率;分析化学实验中可能包含色谱峰面积、保留时间、吸光度、质荷比和校准曲线;材料研究中则可能涉及组成、制备条件、晶体结构、电导率、带隙或循环稳定性。这些数据往往分散在 Excel、CSV、文本文件、实验室信息管理系统(LIMS)或公共数据库中。
Python可以帮助研究者以程序化方式完成以下任务:
o批量读取 CSV、Excel、TXT、JSON等文件;
o统一列名、数据类型、日期格式和单位;
o识别缺失值、重复记录和明显录入错误;
o按实验批次、样品类型、反应条件或化合物类别进行筛选和分组;
o计算平均值、标准差、置信区间、相关系数等描述性统计量;
o将多张数据表按照样品编号、化合物编号或实验编号进行合并;
o将清洗和整理后的数据自动导出,供后续建模、绘图或共享使用。
例如,对于一份反应条件优化数据,研究者可以利用Python自动比较不同催化剂、碱、溶剂和温度下的收率;对于一批紫外—可见吸收光谱数据,可以批量扣除空白、寻找最大吸收峰并输出峰位和吸光度;对于数百次重复实验,也可以按批次计算实验误差,检查仪器漂移或操作者差异。
程序化处理的一个重要优势是可重复性。手工在电子表格中进行筛选、复制、排序和计算虽然方便,但操作过程不易完整记录,也容易因误选单元格、公式引用错误或版本混乱而引入问题。若将处理规则写入Python脚本或Jupyter Notebook,其他研究者便可以检查每一步操作,并在新数据到来后重新执行相同流程。
化学数据处理的过程中,需注意的是,数据表中的一个数值通常并不独立存在。它至少应对应明确的样品、测量方法、实验条件、单位和数据来源。脱离这些元数据的数值,即使能够被程序读取,也未必具有可比较性。
5.1.2 分子结构处理与化学信息学
化学信息学的核心任务之一,是将分子结构转化为计算机可以存储、检索和分析的形式。Python结合RDKit、Open Babel、Mordred 等工具,可有效处理SMILES、SDF、MOL、InChI等常见化学结构表示。
以SMILES为例,字符串CCO可表示乙醇。对计算机而言,它首先是一串字符;但借助化学信息学工具,它可以被解析为包含原子、化学键、芳香性、电荷和立体化学信息的分子图。基于这一分子对象,研究者可以进一步完成:
o计算分子量、LogP、拓扑极性表面积(TPSA)等分子描述符;
o计算氢键供体数、氢键受体数、可旋转键数和环系信息;
o生成 Morgan 指纹等分子指纹,用于相似性计算和机器学习;
o进行子结构搜索,例如筛选含有羧酸、吡啶环或特定药效团的化合物;
o识别重复结构、无效SMILES或不合理的价态;
o绘制二维分子结构图,并建立化合物结构图库;
o对盐形式、电荷状态、芳香性、互变异构和立体化学等进行结构标准化。
这些功能对于药物化学、天然产物化学、催化剂设计和化合物库管理尤为重要。例如,在建立溶解度预测模型前,研究者可先由SMILES计算分子量、脂溶性和极性等描述符;在虚拟筛选中,可先排除不符合基本理化性质要求的候选分子;在专利或文献数据挖掘中,也可通过子结构检索快速定位特定骨架的反应或化合物。
需要注意,分子结构的计算表示并非完全没有歧义。同一化合物可能因盐形式、质子化状态、互变异构、立体化学标注或绘制习惯不同而具有不同的结构字符串。因此,在进行数据合并、相似性分析或模型训练前,应根据研究目标制定合理的结构标准化规则,并保留原始结构记录以便追溯。
5.1.3 计算化学结果分析
量子化学、分子动力学和晶体结构计算通常会产生大量文本输出文件。单个计算任务的输出中可能包含几何优化过程、总能量、原子坐标、轨道能级、振动频率、电荷分布、激发态信息、热力学修正和光谱模拟结果。当计算任务从几个扩展到数百个甚至数千个时,手工打开文件、查找关键词和复制数值将变得低效且容易出错。
Python能够通过文件遍历、字符串匹配和专用解析库批量读取计算结果。例如,可从Gaussian、ORCA、VASP、Quantum ESPRESSO或LAMMPS等软件的输出中提取:
o单点能、优化能和相对能量;
oHOMO、LUMO及其能隙;
o优化后的原子坐标、键长、键角和二面角;
o红外与拉曼振动频率及对应强度;
o电荷、自旋密度、偶极矩和极化率;
o吸收或发射光谱中的激发能和振子强度;
o分子动力学轨迹中的温度、压力、均方位移和径向分布函数。
例如,研究一系列取代芳香化合物的电子性质时,可以编写脚本自动提取每个分子的HOMO与LUMO能量,并与实验氧化还原电位或吸收峰位置进行比较。又如,在催化反应机理研究中,可以批量汇总不同中间体和过渡态的自由能,自动生成反应自由能剖面图。
Python在此类工作中的作用不仅是“提取数值”,还包括建立标准化的数据分析管线。例如,可设置规则检查几何优化是否真正收敛、过渡态是否仅有一个虚频、频率计算是否存在异常,或不同计算任务是否使用了统一的理论水平。这样的质量控制对于保证计算结论的可靠性十分重要。
5.1.4 机器学习建模与人工智能化学
近年来,机器学习已成为人工智能化学的重要技术路径。其基本思想是:从已有的化学数据中学习结构、组成、工艺条件或谱图特征与目标性质之间的统计关系,并将这种关系用于未知样本的预测、筛选或辅助决策。
Python是开展机器学习研究最常用的平台之一。借助scikit-learn、PyTorch、TensorFlow、XGBoost等软件包,研究者可以构建多种模型,用于:
o分子性质预测:如水溶解度、LogP、熔点、毒性、抗氧化活性、光电性质或吸附能力预测;
o反应预测与条件推荐:如反应产物预测、反应类别识别、收率预测、催化剂或溶剂筛选;
o光谱数据分析:如利用红外、拉曼、核磁、质谱或色谱数据进行定性分类和定量预测;
o材料性能预测:如电池材料容量、催化活性、聚合物性能、晶体稳定性和气体吸附性能预测;
o图像与实验过程分析:如显微图像分类、晶体形貌识别、实验视频监测和仪器信号异常检测。
一个典型的化学机器学习流程通常包括以下环节:
- 定义问题:明确需要预测或分类的对象、目标变量和实际应用场景;
- 收集与审查数据:检查数据来源、测量方法、单位、样本分布和潜在偏差;
- 特征构建:将分子结构、反应条件、组成信息或谱图转换为模型可读取的特征;
- 划分训练集、验证集和测试集:避免用测试数据参与模型选择;
- 训练与优化模型:比较不同算法和参数组合;
- 评估模型性能:使用适当指标,如MAE、RMSE、、准确率、召回率或ROC-AUC;
- 解释与验证结果:分析模型适用域、误差来源和化学合理性,并尽可能通过独立实验验证。
应当强调,机器学习模型学习的是训练数据中的统计模式,而不是自动获得化学定律。若数据量过小、标签质量不可靠、训练集与测试集划分不当,或样本之间存在信息泄漏,即使模型报告出很高的评价指标,也可能缺乏真实的预测能力。因此,Python提供的是建模工具;模型是否可信,仍取决于问题定义、数据质量、验证策略和化学解释。
5.1.5 化学数据可视化
可视化是连接数据与科学解释的重要环节。化学研究中的数据往往维度较高、来源复杂,仅靠查看原始表格或一串统计指标,难以直观发现规律、异常和潜在关系。Python的Matplotlib、Seaborn、Plotly等工具可帮助研究者以规范、可复现的方式绘制图形。
常见的化学数据图形包括:
o折线图和动力学曲线:展示浓度、转化率、吸光度或电化学信号随时间的变化;
o标定曲线:展示仪器响应与标准物浓度之间的关系,并用于定量分析;
o散点图和回归图:观察两个变量之间的关系,如LogP与溶解度、温度与收率;
o直方图、核密度图和箱线图:展示某一性质的分布、偏态、离散程度和异常值;
o热图:展示变量相关性、反应条件组合或高通量实验结果;
o柱状图和分组图:比较不同催化剂、溶剂、配体或材料组成下的性能差异;
o降维图和化学空间图:将高维分子描述符或指纹映射到二维平面,观察样本聚类、覆盖范围和类别分布;
o模型评估图:如预测值—实验值对比图、残差图、混淆矩阵和学习曲线。
高质量的科学图形应服务于准确交流,而非单纯追求视觉效果。图中应清楚标明变量名称、单位、样本量、图例和必要的误差信息;颜色选择应兼顾可读性与色觉障碍友好性;坐标轴范围和数据处理方式也应避免造成误导。尤其在展示模型结果时,除了报告拟合优度,还应展示独立测试集表现、误差分布及适用范围。
5.1.6 自动化实验与科学工作流
随着高通量实验、机器人平台和智能仪器的发展,Python在实验室自动化中的作用日益突出。它可以作为不同仪器、数据库、分析程序和决策模型之间的“连接层”,使原本相互独立的实验步骤形成可追踪的数字化工作流。
在条件允许的实验室中,Python可用于:
o读取天平、温控设备、色谱仪、光谱仪或电化学工作站导出的数据;
o按照预设规则生成样品配方、加样体积或反应条件清单;
o调用仪器控制接口,实现自动进样、定时测量或批量实验;
o实时监测仪器信号,并对异常结果发出提示;
o自动整理实验结果,生成日报、图表和标准化记录;
o将实验数据反馈给机器学习模型,用于下一轮候选条件推荐。
这一过程常被概括为“模型预测—实验验证—数据反馈”的迭代闭环。以催化反应条件优化为例,模型可根据已有实验数据推荐若干可能具有较高收率的条件组合;实验人员或自动化平台完成验证后,新数据再被加入训练集,用于更新模型。通过多轮迭代,有望减少无效实验并提高探索效率。
但实验自动化不意味着可以忽略实验设计与安全管理。仪器接口的可靠性、试剂兼容性、异常中止机制、数据备份、人员审核和化学安全边界,都必须在系统设计中得到充分考虑。特别是在涉及易燃、易爆、强腐蚀性或高毒性物质时,自动化程序只能作为辅助工具,不能替代必要的实验室安全规范和人工监督。
5.1.7 Python与化学研究者的专业判断
需要指出的是,Python并不替代化学知识。程序可以高效执行计算、整理数据和生成图形,但研究问题的定义、数据质量的判断、结果的化学解释,以及实验验证方案的设计,仍然依赖研究者的专业判断。
例如,程序可以发现某一反应条件与高收率存在统计关联,却不能仅凭关联就断言该条件是决定性原因;模型可以预测某分子具有较高活性,却不能自动排除其合成不可行、稳定性不足或毒性过高的可能性;可视化图中出现的“异常点”,也可能是录入错误、单位错误、测量误差、批次效应,或真正值得深入研究的新现象。区分这些情况,需要结合化学结构、实验条件、仪器原理、文献证据和重复实验进行综合判断。
因此,学习Python的目标不应只是掌握若干代码命令,而是形成一种更规范的数据思维和研究习惯:明确记录数据来源与处理规则,重视单位和元数据,保留原始记录,检验分析流程的可重复性,并始终让计算结果接受化学原理和实验事实的检验。Python是化学研究的重要工具,而化学问题意识、批判性思维和实验验证始终是科学研究的核心。
5.2 Python环境配置与Jupyter Notebook使用
5.2.1 Python 发行版与环境管理
Python程序的运行依赖于解释器及相应的软件包。对于化学研究而言,除Python本身外,通常还需要安装用于数值计算、表格处理、绘图、机器学习和化学信息学的扩展库。为了避免不同项目之间的软件包版本相互干扰,建议从学习初期就建立“项目独立环境”的习惯。
本节以Anaconda/Miniconda和Jupyter Notebook为例,介绍适合初学者的安装、环境创建、软件包管理与交互式编程的基本方法。命令行示例在Windows、macOS和 Linux中基本通用;个别界面和路径名称可能略有差异。
5.2.1 Python 发行版与环境管理
1. 为什么需要Python发行版和虚拟环境?
Python的“发行版”可以理解为Python解释器及其配套工具的安装方式。化学数据分析中常用的NumPy、Pandas、Matplotlib、RDKit等软件包都需要额外安装。若直接将所有软件包安装在同一个全局环境中,随着项目增多,容易出现以下问题:
o某个项目要求较新的软件包版本,而旧项目依赖较早版本;
o安装新软件包后,已有代码因依赖关系变化而不能运行;
o不同课程、课题组成员或计算机之间的软件环境不一致,导致相同代码得到不同结果,甚至无法运行;
o难以准确记录某项研究实际使用过哪些软件及其版本。
虚拟环境(virtual environment)相当于为每个项目建立相互隔离的软件工作空间。每个环境可以拥有独立的Python版本和软件包组合。例如,课程练习可使用chem_python环境,机器学习项目可使用另一个环境,含有特定量子化学解析工具的项目又可单独建立环境。
对于初学者,推荐使用Anaconda或Miniconda进行环境管理。二者都基于 Conda 包与环境管理工具。
oAnaconda:集成Python、Jupyter、Spyder以及许多常用科学计算软件包,图形界面较完整,适合希望一次性获得较完整科学计算环境的初学者。缺点是安装包体积相对较大。
oMiniconda:只提供Conda、Python及少量基础组件,用户可按需安装所需软件包,体积较小、环境更简洁。对于希望理解环境管理过程、长期开展科研计算的读者,通常更推荐Miniconda。
无论选择哪一种,后续使用的conda命令基本相同。本书以下示例以Miniconda为例;使用 Anaconda的读者可直接参照执行。
2. 安装 Miniconda 或 Anaconda
步骤 1:下载安装程序
访问Anaconda(https://www.anaconda.com/download)或Miniconda(https://docs.anaconda.net.cn/miniconda/install/)的官方网站,选择与本机操作系统和芯片架构相匹配的安装程序。
oWindows:通常选择64位安装程序;
omacOS:需区分Apple Silicon(M系列芯片)与Intel芯片;
oLinux:通常下载对应的安装脚本。
建议优先安装当前稳定版本,并选择Python 3的发行版。对于本章示例,Python 3.12是合适的选择;但若某些专业软件包暂未支持该版本,也可选择Python 3.11。
步骤 2:完成安装
在Windows中,按安装向导操作即可。一般建议:
o选择“仅为当前用户安装”(Just Me),避免不必要的系统权限问题;
o安装路径尽量使用不含中文、空格或特殊字符的目录;
o若安装程序提供“将Anaconda/Miniconda加入系统PATH”的选项,初学者通常不必勾选,可使用Anaconda Prompt或Miniconda Prompt执行命令。
在macOS或Linux中,可在终端中运行下载的安装脚本,并按照提示完成安装。安装后通常需要重新打开终端,或执行初始化命令,使conda命令生效。
步骤 3:验证 Conda 是否可用
安装完成后,打开命令行工具:
Windows:打开Anaconda Prompt或Miniconda Prompt;
macOS/Linux:打开“终端”(Terminal)。
输入:
bash
conda –version
若屏幕显示类似 conda 24.x.x的版本信息,说明Conda已正确安装。随后可输入:
bash
python --version
查看当前可调用的Python版本。
提示:Windows的“命令提示符”、PowerShell和Anaconda Prompt并不完全相同。初学阶段,若使用Conda,优先在Anaconda Prompt或Miniconda Prompt中执行本书命令,通常可以减少环境变量相关的问题。
3. 查看已有环境
Conda安装完成后,系统通常已经包含一个名为base的基础环境。可输入以下任一命令查看所有环境:
conda env list
或
conda info –envs
输出结果可能类似:
# conda environments:
#
base*/Users/your username/miniconda3
其中星号*表示当前正在使用的环境。
4. 创建本章所需的Python环境
下面创建一个名为chem_python的环境,并指定Python版本为3.12:
conda create -n chem_python python=3.12
执行后,Conda 会列出计划安装的软件包,并询问是否继续。
Do you accept the Terms of Service (ToS) for https://repo.anaconda.com/pkgs/main? [(a)ccept/(r)eject/(v)iew]:
输入:a
然后按Enter键确认。
命令中各部分的含义如下:
oconda create:创建新环境;
o-n chem_python:将环境命名为chem_python;
opython=3.12:在该环境中安装Python 3.12。
环境名称可以自行设定,但建议使用简洁、具有明确含义的英文名称,例如 chem_ml、spectra_analysis或reaction_data。环境名中尽量避免使用空格和特殊符号。
若课程统一要求Python 3.11,可将命令改为:
conda create -n chem_python python=3.11
5. 激活与退出环境
创建环境后,需要先“激活”(activate)该环境,才能在其中安装软件包或运行相应的Python程序:
conda activate chem_python
激活成功后,命令行提示符前一般会出现环境名称,例如
(chem_python) C:\Users\username>
或:
(chem_python) username@computer:~$
此时输入:
python --version
应显示该环境中的Python版本。
完成工作后,可退出当前环境:
conda deactivate
若再次执行该命令,通常会返回base环境或完全退出Conda环境,具体取决于当前状态。
6. 安装本章常用软件包
确认已激活chem_python环境后,安装本章常用的数值计算、数据分析、可视化和Notebook工具:conda install numpy pandas matplotlib seaborn jupyter
各软件包的主要用途如下:
软件包 | 主要用途 |
numpy | 多维数组、数值计算、矩阵运算与随机数处理 |
pandas | 表格数据读取、整理、筛选、统计与导出 |
matplotlib | 基础科学绘图与图形定制 |
seaborn | 基于Matplotlib的统计可视化 |
jupyter | Jupyter Notebook的运行与管理组件 |
执行命令后,Conda会自动分析依赖关系,并提示需要安装或更新的软件包列表。确认无误后输入y。
为了在Jupyter中以更现代的界面使用Notebook,也可额外安装JupyterLab:
conda install jupyterlab
JupyterLab与传统Jupyter Notebook都可以完成本书练习。前者支持文件浏览、代码编辑器、终端和多个Notebook的集成式界面;后者界面更简洁,适合初学者理解“单元格—运行结果”的交互式编程方式。
7. 安装RDKit化学信息学工具包
RDKit是化学信息学中广泛使用的开源工具包,可用于读取和处理分子结构、计算描述符与分子指纹、进行子结构检索和绘制二维结构图。由于RDKit依赖多个底层化学与图形库,通常建议通过conda-forge软件源安装,而不建议初学者直接使用普通pip install命令。
在已激活的chem_python环境中执行:
conda install -c conda-forge rdkit
其中:
- -c conda-forge表示从conda-forge社区软件源获取软件包;
- rdkit是待安装的软件包名称。
安装完成后,可输入:
python
进入Python交互式解释器,并依次运行:
python
from rdkit import Chem
mol = Chem.MolFromSmiles("CCO")
print(mol is not None)
若输出:
True
说明RDKit已能正确将乙醇的SMILES 表示CCO解析为分子对象。随后输入:
python
exit()
即可退出Python交互式解释器并返回命令行。
8. 使用pip安装补充软件包
Conda是推荐的主要安装方式,但有些较新的Python软件包可能尚未提供Conda包,或者仅在PyPI平台(https://pypi.org/)发布。这时可在已激活的目标Conda环境中使用:
pip install 软件包名称
例如:
pip install openpyxl
openpyxl常用于读写较新的Excel.xlsx文件。
混用Conda与pip时,应注意以下原则:
- 优先使用conda install安装Conda可提供的软件包;
- 只有Conda中没有合适版本时,再使用pip install;
- 使用pip前必须确认已激活正确的环境;
- 尽量在环境配置基本完成后再使用pip,避免复杂依赖冲突;
- 若环境已出现难以解决的冲突,通常重新创建一个干净环境比反复修补更可靠。
9. 查看、更新与删除软件包
在当前环境中查看已安装的软件包:
conda list
查看某个特定软件包,例如 Pandas:
conda list pandas
更新当前环境中的某个软件包:
conda update pandas
更新Conda自身时,建议回到base环境后执行:
conda activate base
conda update conda
若不再需要某个软件包,可在目标环境中卸载:
conda remove seaborn
若某个项目已经结束,且对应环境不再使用,可删除整个环境:
conda env remove -n chem_python
删除环境前应确认其中没有尚未备份的代码、数据或配置文件。需要强调的是,环境中通常保存的是软件包而非项目数据;但良好的研究习惯仍是将代码、原始数据、处理结果和环境配置文件统一保存在明确的项目目录中。
10. 导出与复现环境配置
为保证课程作业、论文数据分析或课题组协作的可重复性,建议在项目完成或阶段性提交时导出环境配置。首先激活目标环境:
conda activate chem_python
然后执行:
conda env export --from-history > environment.yml
该命令会在当前目录生成environment.yml文件,记录环境名称及主动安装的软件包。其他用户获得该文件后,可通过以下命令创建相近的环境:
conda env create -f environment.yml
若需要导出更完整的软件包版本信息,可使用:
conda env export > environment_full.yml
不过,完整环境文件可能包含与特定操作系统相关的底层依赖,在不同系统之间的可移植性有时较差。对于教材、课程或一般科研协作,--from-history导出的较简洁配置通常更便于共享;对于严格复现特定计算环境,则应同时记录操作系统、Python版本、关键软件包版本及必要的外部软件版本。
5.2.2 Jupyter Notebook 的基本使用
Jupyter Notebook是一种交互式计算环境。它将代码、文字说明、数学公式、图形和运行结果保存在同一文档中,尤其适合化学数据分析、教学演示和可重复研究记录。
一个 Notebook 文件通常以.ipynb为扩展名。例如:
chapter05_data_analysis.ipynb
与传统Python脚本(.py文件)相比,Notebook允许读者分段运行代码,并立即查看表格、图形和中间计算结果。这对于探索数据、检查错误和解释分析过程非常方便。
1. 建立项目文件夹
在启动Jupyter前,先为本章练习建立一个专门目录。例如:
chem_ai_book/
└── chapter05/
├── data/
├── notebooks/
├── figures/
└── output/
其中:
odata/:保存原始数据与清洗后的数据;
onotebooks/:保存.ipynb Notebook文件;
ofigures/:保存导出的图形;
ooutput/:保存分析结果、汇总表或模型输出。
应尽量避免把原始数据、临时代码、图片和最终结果随意堆放在同一目录。清晰的文件结构有助于追踪数据来源,也便于后续复现和共享。
在代码单元格中按Shift + Enter可运行当前单元格并跳转到下一单元格。建议养成以下习惯:
1. 在 Notebook 开头写明数据来源、分析目的、软件版本与作者;
2. 按照“导入库—读取数据—数据检查—处理分析—可视化—结论”的顺序组织代码;
3. 为关键步骤添加Markdown说明和必要注释;
4. 定期执行“Restart Kernel and Run All”,确认代码从头运行时仍可复现结果;
5. 不将原始数据直接覆盖,应保留原始文件并另存处理后的结果。
2. 进入项目目录
打开 Anaconda Prompt、Miniconda Prompt或终端,激活环境:
conda activate chem_python
再使用cd命令进入项目目录。例如,在Windows 中:
cd C:\Users\username\Documents\chem_ai_book\chapter05
在 macOS 或 Linux 中:
cd ~/Documents/chem_ai_book/chapter05
可通过以下命令确认当前所在路径:
pwd
Windows 的某些命令行环境也可使用:
cd
3. 启动Jupyter Notebook或JupyterLab
在项目目录中执行:
jupyter notebook
正常情况下,命令行会显示服务器地址,并自动在默认浏览器中打开 Jupyter Notebook 页面。若浏览器没有自动打开,可复制命令行中以http://localhost:或http://127.0.0.1:开头的地址,粘贴到浏览器地址栏中访问。
若安装的是JupyterLab,也可运行:
jupyter lab
启动后,请保持命令行窗口处于运行状态;关闭该窗口通常会停止Jupyter服务。完成工作后,可在命令行窗口中按:Ctrl + C,然后根据提示输入y,关闭Jupyter服务。
4. 新建Notebook并选择正确的内核
在Jupyter的文件浏览页面中,进入notebooks/文件夹,选择:
New → Python 3
即可新建Notebook。随后可将文件重命名为例如:
5_2_python_basics.ipynb
Notebook运行代码所使用的Python环境称为“内核”(kernel)。必须确认当前Notebook使用的是chem_python环境对应的内核,否则可能发生“命令行中已安装软件包,但Notebook无法导入”的问题。
为了使chem_python环境显示在Jupyter的内核列表中,可在命令行中执行:
conda activate chem_python
conda install ipykernel
python -m ipykernel install --user --name chem_python --display-name "Python (chem_python)"
之后在Notebook菜单中依次选择:
Kernel → Change Kernel → Python (chem_python)
即可切换到本章所需环境。
5. 单元格及其运行方式
Notebook 的基本组成单位是“单元格”(cell),常用类型包括:
o代码单元格(Code cell):用于编写和运行 Python 代码;
oMarkdown 单元格:用于编写标题、说明文字、列表、公式和分析结论;
oRaw 单元格:较少用于初学阶段。
在代码单元格中输入:
print("Hello, chemistry!")
按Shift + Enter运行当前单元格,输出结果为:
Hello, chemistry!
常用快捷键包括:
操作 | 快捷键 |
运行当前单元格并跳转至下一单元格 | Shift + Enter |
运行当前单元格但停留在本单元格 | Ctrl + Enter |
运行当前单元格并在下方新建单元格 | Alt + Enter |
保存 Notebook | Ctrl + S或Cmd + S |
中断正在运行的代码 | 菜单Kernel → Interrupt |
重启内核 | 菜单Kernel → Restart |
在Markdown单元格中,可输入:
# 反应收率数据分析
本Notebook用于比较不同温度下的反应收率。
运行后将显示为排版后的标题和正文。合理使用Markdown记录研究目的、数据来源、处理步骤、图形解释与结论,是Notebook用于可重复研究的关键。
6. 第一个化学数据分析Notebook
新建一个代码单元格,输入以下代码:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
print("科学计算环境已成功加载。")
运行后若没有报错,说明 NumPy、Pandas、Matplotlib 和 Seaborn 已正确安装,并且当前Notebook使用了正确的内核。
下面建立一组示例反应数据:
reaction_data = pd.DataFrame({
"temperature_C": [25, 40, 60, 80, 100],
"yield_percent": [35.2, 52.8, 76.4, 88.1, 81.5]
})
reaction_data
Notebook会将DataFrame以表格形式显示。

接着绘制温度与收率的关系图:
plt.figure(figsize=(6, 4))
plt.plot(
reaction_data["temperature_C"],
reaction_data["yield_percent"],
marker="o"
)
plt.xlabel("Temperature (°C)")
plt.ylabel("Yield (%)")
plt.title("Effect of Temperature on Reaction Yield")
plt.grid(True, alpha=0.3)
plt.show()
最终绘制的图如下图所示:

该示例表明,在给定实验条件下,收率随温度升高先增加、后略有下降。需要注意的是,这一图形只能描述这组实验数据中的变化趋势;若要判断温度效应是否显著,还需考虑重复实验、误差范围、其他变量是否受控,以及可能的副反应或底物分解等化学因素。
7. 保存、导出与运行顺序
Notebook会自动保存,但仍建议经常按Ctrl + S(macOS为Cmd + S)手动保存。提交作业或归档研究记录时,通常应同时保存:
1..ipynb文件:保留可编辑代码、文字和运行结果;
2.导出的.html或.pdf文件:便于他人直接阅读;
3.所使用的原始数据文件;
4.环境配置文件,如environment.yml。
在Jupyter中可通过:
File → Save and Export Notebook As → HTML
将Notebook导出为HTML文件。
Notebook的一个常见问题是:不同单元格可能被以任意顺序运行。这样,某些变量虽然在当前页面中存在,却依赖于前面未展示或未按顺序执行的代码,从而使他人难以复现结果。完成Notebook后,建议执行:
Kernel → Restart Kernel and Run All Cells
或类似的“重启内核并运行全部单元格”操作。若Notebook能从头到尾无报错运行,通常说明其运行顺序较为完整。
5.2.3 常见问题与初步排查
问题 1:输入conda后提示“不是内部或外部命令”
这通常意味着Conda未正确安装,或当前命令行未完成初始化。Windows用户可优先改用Anaconda Prompt/Miniconda Prompt;macOS/Linux用户可尝试重新打开终端,或按照安装程序提示执行conda init后重启终端。
问题 2:已安装软件包,但Notebook中出现ModuleNotFoundError
最常见原因是Notebook使用的内核与安装软件包时使用的Conda环境不同。应在命令行激活目标环境后安装ipykernel,并在Notebook中切换到对应内核。
可在Notebook单元格中运行:
import sys
print(sys.executable)
查看Notebook当前实际使用的Python路径,并与命令行中激活环境的路径进行核对。
问题 3:安装 RDKit 时出现依赖冲突或无法找到软件包
可首先确认已激活正确环境,并使用conda-forge软件源:
bash
conda activate chem_python
conda install -c conda-forge rdkit
若仍不能解决,可尝试创建Python 3.11的新环境,再重新安装所需软件包。不要在已经安装大量软件、且依赖关系复杂的环境中盲目反复安装和卸载。
问题 4:浏览器关闭后,Notebook是否丢失?
只要已保存.ipynb文件,浏览器关闭通常不会删除Notebook内容;但若命令行中的Jupyter服务也已停止,下次需要重新激活环境、进入项目目录并再次运行jupyter notebook或jupyter lab。
问题 5:代码单元格一直显示[*],没有结果
[*]表示内核正在运行代码。若计算时间明显异常,可选择:
Kernel → Interrupt
中断执行。若内核无响应,可选择重启内核。重启后,内存中的变量会被清除,需要从前面的单元格重新运行代码。
5.2.4 环境配置的实践建议
- 一个项目,一个环境。对课程练习、论文项目或不同课题分别建立环境,减少版本冲突。
- 先激活环境,再安装软件包。这是避免“装了却无法导入”问题的最重要习惯之一。
- 保留环境配置文件。在论文投稿、代码共享或课题组交接时,environment.yml与代码、数据同样重要。
- 保留原始数据,避免直接覆盖。数据清洗后的文件应另存,并在Notebook中清楚记录处理步骤。
- Notebook不只是代码容器。应使用Markdown记录问题背景、数据来源、方法参数、图形解释和结论。
- 从头运行验证可重复性。在提交或发表前,重启内核并运行全部单元格,检查分析流程是否完整。
- 谨慎更新关键环境。正在用于课题或论文的数据分析环境不宜随意整体升级;更新前应备份环境文件和项目代码。
通过上述配置,读者即可建立一个能够进行化学表格数据处理、基础统计分析、科学绘图以及初步分子结构处理的Python工作环境。后续章节将在此基础上,逐步介绍Python的基本语法、数据处理方法与化学信息学应用。
5.3 Python基础语法
Python的基础语法是后续数据处理、可视化、化学信息学与机器学习编程的共同基础。对于化学研究者而言,学习这些语法的重点不在于记忆全部命令,而在于能够将实验记录、分子性质、计算结果和分析规则转化为清晰、可执行且便于检查的程序。
本节首先介绍变量、常见数据类型和基本运算符。后续将进一步讨论列表、字典、条件判断、循环和函数等内容。
5.3.1 变量、数据类型与运算符
1. 变量:为数据赋予可引用的名称
变量可以理解为存储数据的名称,或者说是指向某个数据对象的“标签”。在化学数据分析中,变量可以保存化合物名称、浓度、温度、收率、谱图峰位、计算能量或分子结构字符串等信息。
Python不需要在定义变量时显式声明数据类型,程序会根据赋值内容自动识别。例如:
compound_name = "caffeine"# 字符串
molar_mass = 194.19# 浮点数,单位:g/mol
atom_number = 24# 整数
is_active = True# 布尔值
print(compound_name)
print(molar_mass)
运行结果为:
caffeine
194.19
上述代码中,compound_name、molar_mass、atom_number 和 is_active 都是变量名;等号右侧分别是被赋予变量的数据。这里的 = 表示“赋值”,即将右侧的值保存到左侧变量名所对应的位置,并不表示数学意义上的相等。
变量的值可以在程序运行过程中被重新赋值。例如,某反应体系的温度可由室温调整为加热条件:
temperature_C = 25
print(temperature_C)
temperature_C = 80
print(temperature_C)
输出为:
25
80
第二次赋值后,变量temperature_C保存的值由25更新为80。这一特性使程序能够方便地记录实验过程中的状态变化或循环计算中的中间结果。
2. 变量命名规则与建议
Python对变量命名有若干基本规则:
o变量名可由字母、数字和下划线_组成;
o变量名不能以数字开头;
o变量名不能包含空格、连字符-或大多数特殊符号;
o变量名不能使用Python的保留关键字,如if、for、while、class和True;
oPython区分大小写,yield_percent与Yield_percent是两个不同的变量。
例如,下列命名是合法的:
reaction_time
sample_01
homo_energy
concentration_mol_L
下列命名则不合法或不推荐:
1st_sample = "A"# 不合法:不能以数字开头
reaction-yield = 85.2# 不合法:连字符会被解释为减号
reaction yield = 85.2# 不合法:变量名中不能有空格
通常采用能够反映化学含义的英文变量名,并使用下划线连接多个单词,即所谓的snake_case命名风格。例如:
boiling_point_C = 78.37
reaction_yield_percent = 86.5
smiles_string = "CCO"
相比x、a1或data2这类含义模糊的名称,清晰的变量名更有利于代码阅读、协作和后期维护。变量名中也可包含单位信息,如_C、_K、_mol_L或_eV;这有助于减少单位混淆,但不能替代必要的单位核查。
需要注意的是,Python变量本身通常不携带物理单位。程序可以正确计算25 * 0.1,却无法自动判断这两个数分别表示摄氏温度、开尔文温度、体积还是浓度。因此,化学计算中必须由研究者明确记录并统一单位。
3. 查看变量的类型
可使用内置函数type()查看变量当前的数据类型:
compound_name = "caffeine"
molar_mass = 194.19
atom_number = 24
is_active = True
print(type(compound_name))
print(type(molar_mass))
print(type(atom_number))
print(type(is_active))
输出类似于:
<class 'str'>
<class 'float'>
<class 'int'>
<class 'bool'>
在处理从 CSV、Excel或仪器文件读取的数据时,检查类型尤其重要。例如,看起来像数值的浓度列,有时可能因包含单位字符、缺失值标记或异常符号而被读作字符串;此时若直接进行计算,可能导致报错或产生错误结果。
4. 常见基本数据类型
Python中常用的基本数据类型如下表所示。
类型 | 示例 | 化学数据中的可能用途 |
int | 25 | 温度编号、原子序数、样本编号、重复实验次数 |
float | 7.35 | pH、吸光度、能量、浓度、收率 |
str | "CCO" | 化合物名称、SMILES、仪器文件名、样品标签 |
bool | True、False | 是否通过质量控制、是否有活性、是否收敛 |
NoneType | None | 尚未记录、未知或暂时缺失的值 |
(1)整数:int
整数(integer)是不含小数部分的数值,例如:
atomic_number = 8
number_of_replicates = 3
在化学中,原子序数、样品编号、重复测定次数或分子中某类原子的计数,通常可用整数表示。
(2)浮点数:float
浮点数(floating-point number)用于表示带小数部分的数值:
pH = 7.35
concentration = 0.020
energy_eV = -5.42
浓度、质量、温度、吸光度、反应收率和计算能量等连续型实验数据通常以浮点数保存。应注意,计算机中的浮点数采用有限精度表示,少数小数在二进制系统中无法完全精确表达。因此,某些计算结果可能出现非常接近零但不严格等于零的情况。
例如:
print(0.1 + 0.2)
在某些情况下可能显示:
0.30000000000000004
这并非化学计算错误,而是浮点数表示方式造成的数值现象。在一般数据分析中可通过合理的显示精度或容差判断处理;对于高精度计量、货币或严格数值计算,则需采用更专门的方法。
(3)字符串:str
字符串(string)用于保存文本信息,必须用单引号或双引号括起来:
compound_name = "ethanol"
smiles = "CCO"
solvent = 'dimethyl sulfoxide'
在化学研究中,化合物名称、CAS号、样品编号、SMILES、InChI、仪器文件路径和实验备注等通常以字符串形式保存。
字符串中的数字仍然是文本,而不是可直接参与数值运算的数。例如:
yield_text = "85.2"
print(type(yield_text))
输出为:
<class 'str'>
若需要将其用于计算,应先转换为浮点数:
yield_percent = float(yield_text)
print(yield_percent + 4.8)
输出为:
90.0
(4)布尔值:bool
布尔值只有两种可能:True(真)和 False(假)。其首字母必须大写。布尔值常用于记录某项条件是否成立,或控制程序的执行逻辑:
is_pure = True
has_converged = False
例如,可记录一个量子化学优化任务是否收敛:
optimization_converged = True
print(optimization_converged)
布尔值也是后续条件判断语句的基础。
(5)空值:None
None表示“没有值”或“尚未指定”。它并不等同于数值零0,也不等同于空字符串""。
melting_point_C = None
print(melting_point_C)
print(type(melting_point_C))
输出为:
None
<class 'NoneType'>
例如,某化合物的熔点尚未测定,或某一实验结果等待录入时,可暂时用None表示。处理真实表格数据时,缺失值还可能表现为NaN,后续使用Pandas时将进一步讨论。
5. 数据类型转换
在实际数据处理中,经常需要在不同类型之间转换。常用的转换函数包括:
oint():转换为整数;
ofloat():转换为浮点数;
ostr():转换为字符串;
obool():转换为布尔值。
例如:
temperature_text = "80"
temperature_C = float(temperature_text)
print(temperature_C)
print(type(temperature_C))
输出为:
80.0
<class 'float'>
将浮点数转换为整数时,小数部分会被直接舍去,而不是按通常的四舍五入规则处理:
yield_percent = 86.9
print(int(yield_percent))
输出为:
86
如果需要四舍五入,可使用 round():
yield_percent = 86.9
print(round(yield_percent))
输出为:
87
类型转换必须符合数据本身的含义。以下代码会报错:
compound_name = "caffeine"
float(compound_name)
因为普通化合物名称不能转换为数值。对于实验数据表中的异常字符,如"below detection limit"、"--"或"not measured",也不能简单地强制转换为数值,而应结合数据记录规范进行清洗和标注。
6. 算术运算符
Python中常用的算术运算符包括+、-、*、/、**和%,分别表示加、减、乘、除、乘方和取余。
concentration = 0.20# mol/L
volume = 0.050# L
amount = concentration * volume
print("物质的量:", amount, "mol")
print("浓度平方:", concentration ** 2)
输出为:
物质的量:0.010000000000000002 mol
浓度平方:0.04000000000000001
从化学意义看,以上计算使用关系式:
其中,为物质的量,为浓度,为体积。程序中体积以L表示、浓度以mol/L表示,因此计算结果的单位为mol。
常见算术运算符及其示例如下:
运算符 | 含义 | 示例 | 示例结果 |
+ | 加法 | 3 + 2 | 5 |
- | 减法 | 3 - 2 | 1 |
* | 乘法 | 3 * 2 | 6 |
/ | 除法 | 3 / 2 | 1.5 |
// | 整除 | 7 // 3 | 2 |
% | 取余 | 7 % 3 | 1 |
** | 乘方 | 3 ** 2 | 9 |
例如,计算溶液中溶质的质量:
concentration_mol_L = 0.50
volume_L = 0.025
molar_mass_g_mol = 58.44
amount_mol = concentration_mol_L * volume_L
mass_g = amount_mol * molar_mass_g_mol
print("所需 NaCl 的质量:", mass_g, "g")
这里应特别注意:代码会执行数值运算,但不会自动检查所用摩尔质量是否对应目标物质,也不会自动识别体积单位是 mL 还是 L。若体积原始记录为 25 mL,则必须先换算为 0.025 L,再代入计算。
7. 运算优先级与括号
Python的基本运算优先级与常规数学规则相近:乘方通常优先于乘除,乘除优先于加减。使用括号可明确控制计算顺序。
result_1 = 2 + 3 * 4
result_2 = (2 + 3) * 4
print(result_1)
print(result_2)
输出为:
14
20
在化学计算中,建议对包含多个运算步骤的公式适当添加括号,以提高可读性并避免歧义。例如,计算某组分的摩尔分数:
n_A = 0.20
n_B = 0.80
mole_fraction_A = n_A / (n_A + n_B)
print(mole_fraction_A)
8. 比较运算符
比较运算符用于判断两个值之间的关系,运算结果为布尔值True或False。常用比较运算符包括>、<、>=、<=、==和!=。
yield_percent = 82.5
print(yield_percent >= 80)
输出为:
True
常用比较运算符如下:
运算符 | 含义 | 示例 |
> | 大于 | yield_percent > 80 |
< | 小于 | pH < 7 |
>= | 大于或等于 | purity >= 95 |
<= | 小于或等于 | temperature_C <= 100 |
== | 等于 | solvent == "ethanol" |
!= | 不等于 | status != "failed" |
例如:
purity_percent = 97.8
print(purity_percent >= 95)
solvent = "ethanol"
print(solvent == "water")
输出为:
True
False
这里需特别注意:= 表示赋值,而 == 表示比较二者是否相等。这是初学者最常见的错误之一。
yield_percent = 85.0# 将 85.0 赋值给变量
is_high_yield = yield_percent >= 80# 判断是否达到 80%
print(is_high_yield)
9. 逻辑运算符
多个条件需要同时判断时,可使用逻辑运算符:
and:两个条件都为真时,结果为真;
or:至少一个条件为真时,结果为真;
not:对布尔值取反。
例如,设定一项初步质量控制规则:收率不低于80%,且产物纯度不低于95%。
yield_percent = 85.0
purity_percent = 97.0
passes_qc = (yield_percent >= 80) and (purity_percent >= 95)
print(passes_qc)
输出为:
True
再如,判断实验温度是否超出某一常规操作范围:
temperature_C = 120
is_outside_range = (temperature_C < 0) or (temperature_C > 100)
print(is_outside_range)
输出为:
True
逻辑运算可以帮助程序根据实验规则进行初步筛选,但阈值必须具有明确的科学依据。例如,不能仅因收率低于某个任意设定值,就将实验结果简单视为“无效”;低收率可能反映真实的反应性差异、底物限制或值得进一步研究的副反应路径。
10. 字符串的基本操作与格式化输出
字符串可使用+进行拼接:
compound_name = "ethyl acetate"
message = "Current compound: " + compound_name
print(message)
输出为:
Current compound: ethyl acetate
但当字符串与数值同时输出时,推荐使用f-string(格式化字符串),其表达更清晰:
compound_name = "caffeine"
molar_mass = 194.19
print(f"The molar mass of {compound_name} is {molar_mass} g/mol.")
输出为:
The molar mass of caffeine is 194.19 g/mol.
f-string还可以控制数值显示的小数位数。例如,将反应收率显示为两位小数:
yield_percent = 86.4567
print(f"Reaction yield: {yield_percent:.2f}%")
输出为:
Reaction yield: 86.46%
这里的:.2f表示以浮点数格式显示,并保留两位小数。需要区分“显示时四舍五入”与“原始数据被修改”:上述操作只改变输出格式,变量yield_percent中保存的原始数值仍为86.4567。
11. 一个简单的化学计算示例
下面以配制溶液为例,综合使用变量、浮点数、算术运算和格式化输出。假设需要配制 100.0 mL、浓度为 0.0500 mol/L 的葡萄糖溶液,葡萄糖摩尔质量为 180.16 g/mol。
compound_name = "glucose"
concentration_mol_L = 0.0500
volume_mL = 100.0
molar_mass_g_mol = 180.16
# 将体积从 mL 转换为 L
volume_L = volume_mL / 1000
# 计算物质的量和所需质量
amount_mol = concentration_mol_L * volume_L
mass_g = amount_mol * molar_mass_g_mol
print(f"Compound: {compound_name}")
print(f"Required amount: {amount_mol:.4f} mol")
print(f"Required mass: {mass_g:.4f} g")
输出为:
Compound: glucose
Required amount: 0.0050 mol
Required mass: 0.9008 g
该程序对应的计算过程为:



在实际实验中,还应根据试剂纯度、结晶水组成、称量误差和定容操作等因素对计算结果进行必要修正。Python能够降低重复计算和抄录错误的概率,但不能替代对化学对象、实验条件和有效数字的专业判断。
5.3.2 列表、元组、字典与集合
在化学研究中,数据通常不是一个孤立的数值,而是由多个相关元素组成的集合。例如,一组反应温度、一批化合物名称、一条实验记录或一组待筛选的溶剂。Python提供了多种内置数据结构,用于组织这类数据。
本节介绍四种常用的数据结构:
o列表(list):有序、可修改,可包含重复元素;
o元组(tuple):有序,通常不修改,可表示固定数据;
o字典(dict):以“键—值”形式组织数据,适合表示结构化记录;
o集合(set):元素不重复,适合去重和集合运算。
选择合适的数据结构,有助于使程序更加清晰,也能减少数据处理中的错误。
(1)列表
列表(list)是Python中最常用的数据结构之一。列表中的元素按照一定顺序排列,可以保存整数、浮点数、字符串,甚至其他列表或字典。列表创建后可以增加、删除和修改元素。
例如,
temperatures = [25, 40, 60, 80]
yields = [45.2, 61.8, 78.4, 73.1]
print(temperatures)
print(yields)
输出为:
[25, 40, 60, 80]
[45.2, 61.8, 78.4, 73.1]
在这个例子中,temperatures和yields分别表示不同温度条件及对应的实验收率。若两个列表中的元素一一对应,则应保持它们的长度和排列顺序一致:
print(len(temperatures))
print(len(yields))
输出均为:
4
这里的len()函数用于获得列表中元素的数量。
1. 列表索引
Python 的索引从0开始。列表中的第一个元素索引为0,第二个元素索引为1,依此类推。
temperatures = [25, 40, 60, 80]
print(temperatures[0])# 第一个元素
print(temperatures[1])# 第二个元素
print(temperatures[3])# 第四个元素
输出为:
25
40
80
这里,temperatures[0]表示第一个元素,而不是“第零个实验条件”。索引从零开始是Python和许多其他编程语言的重要特征。
列表也支持负索引:
print(temperatures[-1])# 最后一个元素
print(temperatures[-2])# 倒数第二个元素
输出为:
80
60
常见索引关系如下:
表达式含义
data[0]# 第一个元素
data[1]#第二个元素
data[-1]#最后一个元素
data[-2]#倒数第二个元素
若索引超出列表范围,会产生IndexError:
print(temperatures[4])
因为列表只有索引0到3,不存在索引为4的元素。
2. 修改列表元素
列表是可变对象,可以直接通过索引修改元素:
yields = [45.2, 61.8, 78.4, 73.1]
yields[1] = 64.5
print(yields)
输出为:
[45.2, 64.5, 78.4, 73.1]
在实际实验数据处理中,可以利用这一特性修正已经确认的录入错误。例如,若确认第二个收率原始记录应为64.5%,可进行修改。但对于原始实验数据,通常不建议直接覆盖原始列表或原始文件,而应保留原始值并另建清洗后的数据对象,以便数据追溯。
3. 向列表中添加元素
使用append()可以在列表末尾添加一个元素:
yields = [45.2, 61.8, 78.4, 73.1]
yields.append(75.6)
print(yields)
输出为:
[45.2, 61.8, 78.4, 73.1, 75.6]
如果需要一次添加多个元素,可使用extend():
temperatures = [25, 40, 60]
temperatures.extend([80, 100])
print(temperatures)
输出为:
[25, 40, 60, 80, 100]
append()和extend()的行为不同:
data_1 = [1, 2, 3]
data_1.append([4, 5])
print(data_1)
输出为:
[1, 2, 3, [4, 5]]
这里整个列表[4, 5] 被作为一个元素添加。
data_2 = [1, 2, 3]
data_2.extend([4, 5])
print(data_2)
输出为:
[1, 2, 3, 4, 5]
这里[4, 5]中的元素被逐个添加。
4. 删除列表元素
可以使用remove()删除指定值:
solvents = ["water", "ethanol", "acetone", "water"]
solvents.remove("water")
print(solvents)
输出为:
['ethanol', 'acetone', 'water']
remove()只删除第一次出现的目标元素。如果目标元素不存在,会产生错误。
使用pop()可以删除指定索引处的元素,并返回被删除的值:
temperatures = [25, 40, 60, 80]
removed_temperature = temperatures.pop(1)
print(removed_temperature)
print(temperatures)
输出为:
40
[25, 60, 80]
如果不指定索引,pop()默认删除最后一个元素:
last_temperature = temperatures.pop()
print(last_temperature)
使用del也可以删除元素:
temperatures = [25, 40, 60, 80]
del temperatures[0]
print(temperatures)
若要清空整个列表,可使用:
temperatures.clear()
print(temperatures)
5. 列表切片
切片(slicing)用于提取列表中的一部分元素,基本形式为:
list[start:stop]
其中,start是起始索引,stop是终止索引,但不包括stop对应的元素。
temperatures = [25, 40, 60, 80, 100]
print(temperatures[1:4])
输出为:
[40, 60, 80]
索引1、2和3被提取,而索引4对应的100不包含在结果中。
常见切片形式如下:
data = [10, 20, 30, 40, 50]
print(data[:3])# 前三个元素
print(data[2:])# 从索引 2 到末尾
print(data[:])# 复制全部元素
print(data[::2])# 每隔一个元素取一个
print(data[::-1])# 逆序
输出为:
[10, 20, 30]
[30, 40, 50]
[10, 20, 30, 40, 50]
[10, 30, 50]
[50, 40, 30, 20, 10]
在光谱数据处理中,切片可用于截取指定波长范围:
wavelengths = [400, 450, 500, 550, 600, 650, 700]
absorbance = [0.12, 0.18, 0.35, 0.60, 0.42, 0.22, 0.10]
selected_wavelengths = wavelengths[1:5]
selected_absorbance = absorbance[1:5]
print(selected_wavelengths)
print(selected_absorbance)
不过,实际处理连续波长数据时,按索引切片要求数据顺序和索引对应关系正确。对于大型表格数据,后续使用 Pandas 的条件筛选通常更稳妥。
6. 列表中的元素是否存在
可以使用in和not in判断某个元素是否存在于列表中:
solvents = ["water", "ethanol", "acetone"]
print("ethanol" in solvents)
print("toluene" in solvents)
输出为:
True
False
这对于检查某个化合物、溶剂或元素是否已经出现在数据记录中非常有用。
7. 列表排序
使用sort()可以直接改变原列表的顺序:
yields = [78.4, 45.2, 61.8, 73.1]
yields.sort()
print(yields)
输出为:
[45.2, 61.8, 73.1, 78.4]
降序排列可使用:
yields.sort(reverse=True)
print(yields)
如果不希望修改原列表,可以使用sorted()创建一个新的排序结果:
yields = [78.4, 45.2, 61.8, 73.1]
sorted_yields = sorted(yields)
print(yields)
print(sorted_yields)
在整理实验记录时,应注意:如果收率列表和温度列表相互对应,单独排序收率会破坏二者之间的配对关系。因此,成对数据通常不能简单地分别排序。
(2)元组
元组(tuple)与列表类似,也是一种有序的数据集合。但元组创建后通常不应修改,因此适合存储固定的实验条件、坐标、分子组成或参数组合。
reaction_condition = (80, "ethanol", "N2")
print(reaction_condition)
print(reaction_condition[0])
输出为:
(80, 'ethanol', 'N2')
80
这里元组可以表示:
反应温度 = 80 °C
溶剂 = ethanol
保护气氛 = N2
元组同样支持索引和切片:
reaction_condition = (80, "ethanol", "N2")
print(reaction_condition[1])
print(reaction_condition[:2])
输出为:
ethanol
(80, 'ethanol')
1. 元组不可修改
下面的代码会产生 TypeError:
reaction_condition = (80, "ethanol", "N2")
reaction_condition[0] = 90
如果实验条件确实需要修改,应创建一个新的元组:
reaction_condition = (80, "ethanol", "N2")
new_condition = (90, reaction_condition[1], reaction_condition[2])
print(new_condition)
元组的不可变性可以防止关键参数在程序运行过程中被意外修改。例如,某个计算流程中的固定参数可使用元组保存:
molecule_coordinates = (1.25, -0.40, 2.16)
2. 元组解包
元组中的多个元素可以同时赋值给多个变量,这称为“解包”(unpacking):
reaction_condition = (80, "ethanol", "N2")
temperature_C, solvent, atmosphere = reaction_condition
print(temperature_C)
print(solvent)
print(atmosphere)
输出为:
80
ethanol
N2
解包要求左侧变量的数量与元组中的元素数量匹配:
condition = (80, "ethanol", "N2")
temperature_C, solvent = condition
上述代码会产生错误,因为左侧只有两个变量,而元组包含三个元素。
元组解包常用于表示坐标:
atom_position = (1.25, -0.40, 2.16)
x, y, z = atom_position
print(f"x = {x}, y = {y}, z = {z}")
3. 单元素元组
创建只有一个元素的元组时,必须加逗号:
single_item_tuple = ("ethanol",)
print(type(single_item_tuple))
如果不加逗号:
not_a_tuple = ("ethanol")
print(type(not_a_tuple))
得到的将是字符串,而不是元组。括号本身并不能决定对象是元组,逗号才是关键。
4. 列表与元组的选择
可以根据数据是否需要修改来选择:
o实验结果会持续增加:使用列表;
o固定的反应条件组合:使用元组;
o一组三维坐标:使用元组;
o需要动态编辑的数据记录:使用列表。
例如:
temperature_series = [25, 40, 60, 80]# 可继续添加新温度
fixed_condition = (80, "ethanol", "N2")# 固定条件
(3)字典
字典(dict)以“键—值”(key-value)对的形式存储信息。与列表通过数字索引访问元素不同,字典通过具有意义的键访问对应的值,因此特别适合表达一条具有多个字段的化学记录。
compound = {
"name": "aspirin",
"smiles": "CC(=O)Oc1ccccc1C(=O)O",
"molar_mass": 180.16,
"logS": -1.55
}
print(compound["name"])
print(compound["molar_mass"])
输出为:
aspirin
180.16
这个字典可以表示阿司匹林的一条简化记录:
键 | 值 |
name | "aspirin" |
smiles | "CC(=O)Oc1ccccc1C(=O)O" |
molar_mass | 180.16 |
logS | -1.55 |
键通常使用字符串,也可以使用整数或其他不可变对象。一个字典中的键必须唯一。
1. 读取字典中的值
使用键访问对应值:
print(compound["smiles"])
print(compound["logS"])
如果访问不存在的键,会产生KeyError:
print(compound["melting_point"])
当某个字段可能不存在时,可以使用get():
melting_point = compound.get("melting_point")
print(melting_point)
由于melting_point不存在,结果为:
None
也可以指定默认值:
melting_point = compound.get("melting_point", "not available")
print(melting_point)
输出为:
not available
使用get()适合处理字段不完整的数据记录,可以避免程序因一个缺失字段而立即中止。
2. 增加和修改字典元素
为字典中不存在的键赋值,可以增加新的字段:
compound["melting_point_C"] = 136
print(compound)
如果键已经存在,则会修改该键对应的值:
compound["logS"] = -1.42
print(compound["logS"])
因此,字典可以方便地逐步补充实验或计算结果:
compound = {
"name": "aspirin",
"smiles": "CC(=O)Oc1ccccc1C(=O)O"
}
compound["molar_mass"] = 180.16
compound["purity_percent"] = 99.2
compound["measured_by"] = "HPLC"
print(compound)
3. 删除字典元素
可以使用del删除指定键:
del compound["measured_by"]
也可以使用pop()删除并返回对应值:
purity = compound.pop("purity_percent")
print(purity)
若需要清空整个字典,可使用:
compound.clear()
删除操作应谨慎进行,尤其是在处理原始实验记录时。更稳妥的方式通常是复制一份记录,再对副本进行修改。
4. 查看键、值和键值对
字典提供了常用方法:
compound = {
"name": "aspirin",
"smiles": "CC(=O)Oc1ccccc1C(=O)O",
"molar_mass": 180.16
}
print(compound.keys())
print(compound.values())
print(compound.items())
分别用于查看所有键、所有值以及所有键值对。
也可以判断某个键是否存在:
print("smiles" in compound)
print("logP" in compound)
输出为:
True
False
注意,使用in判断字典时,默认检查的是键,而不是值:
print("aspirin" in compound)# 检查键中是否有 "aspirin"
如果需要检查某个值是否存在,可使用:
print("aspirin" in compound.values())
5. 遍历字典
可以使用for语句依次读取字典中的内容:
for key, value in compound.items():
print(key, ":", value)
可能输出:
name : aspirin
smiles : CC(=O)Oc1ccccc1C(=O)O
molar_mass : 180.16
也可以只遍历键或值:
for key in compound.keys():
print(key)
for value in compound.values():
print(value)
Python 3.7及以后版本通常保持字典的插入顺序,但在科学计算中不应仅依赖字典顺序来表达数据之间的对应关系。若数据具有明确的表格结构,应考虑使用Pandas的DataFrame。
6. 嵌套字典与列表
字典的值可以是列表,列表中的元素也可以是字典。因此,可以用嵌套结构表示多个化合物:
compounds = [
{
"name": "ethanol",
"smiles": "CCO",
"molar_mass": 46.07
},
{
"name": "acetone",
"smiles": "CC(=O)C",
"molar_mass": 58.08
}
]
print(compounds[0]["name"])
print(compounds[1]["smiles"])
输出为:
ethanol
CC(=O)C
这种结构与JSON数据格式相似,常见于化学数据库接口、实验自动化系统和机器学习数据交换文件中。
也可以使用字典存储多个实验结果:
reaction_results = {
"experiment_001": {
"temperature_C": 60,
"yield_percent": 78.4
},
"experiment_002": {
"temperature_C": 80,
"yield_percent": 88.1
}
}
print(reaction_results["experiment_002"]["yield_percent"])
输出为:
88.1
虽然嵌套字典很灵活,但层级过多时会降低可读性。对于大量实验记录,通常应将数据整理为 Pandas DataFrame。
(4)集合
集合(set)是一种不包含重复元素的数据结构,适合用于去重、成员判断和集合运算。集合中的元素没有可供用户依赖的顺序,因此不能像列表一样通过索引访问。
solvents = {"ethanol", "water", "acetone", "ethanol"}
print(solvents)
输出可能为:
{'ethanol', 'water', 'acetone'}
重复出现的 "ethanol" 只保留一次。
注意:集合不适合表示需要保持实验顺序的数据。例如,反应步骤、时间序列和按编号排列的样品应使用列表,而不是集合。
1. 创建集合与去重
可以将列表转换为集合以去除重复值:
solvent_list = ["water", "ethanol", "acetone", "water", "ethanol"]
unique_solvents = set(solvent_list)
print(unique_solvents)
如果还需要恢复为列表:
unique_solvent_list = list(unique_solvents)
print(unique_solvent_list)
需要注意,转换为集合后,原列表的顺序信息可能不再保留。如果既要去重又要保留首次出现顺序,可使用:
solvent_list = ["water", "ethanol", "acetone", "water", "ethanol"]
unique_solvents = list(dict.fromkeys(solvent_list))
print(unique_solvents)
输出为:
['water', 'ethanol', 'acetone']
2. 向集合中添加和删除元素
使用add()添加一个元素:
elements = {"C", "H", "O"}
elements.add("N")
print(elements)
使用remove()删除元素:
elements.remove("N")
如果不确定元素是否存在,建议使用discard(),因为元素不存在时不会报错:
elements.discard("S")
集合还可以使用clear()清空:
elements.clear()
3. 集合成员判断
集合特别适合进行快速成员判断:
available_solvents = {"water", "ethanol", "acetone"}
print("ethanol" in available_solvents)
print("toluene" not in available_solvents)
集合可用于检查某个待用溶剂是否已经列入实验室库存,也可用于判断一批分子中是否含有某些特定元素符号。
4. 集合运算
集合支持交集、并集和差集等运算。
设两个课题组分别使用过以下溶剂:
solvents_group_A = {"water", "ethanol", "acetone"}
solvents_group_B = {"ethanol", "toluene", "hexane"}
求并集:
all_solvents = solvents_group_A | solvents_group_B
print(all_solvents)
并集表示两个集合中所有不重复的元素。
求交集:
common_solvents = solvents_group_A & solvents_group_B
print(common_solvents)
交集表示两个集合共同包含的元素,结果为:
{'ethanol'}
求差集:
only_A = solvents_group_A - solvents_group_B
print(only_A)
差集表示存在于集合 A 但不在集合 B 中的元素。
也可以使用方法形式表达:
print(solvents_group_A.union(solvents_group_B))
print(solvents_group_A.intersection(solvents_group_B))
print(solvents_group_A.difference(solvents_group_B))
集合运算在化学数据处理中具有多种用途,例如:
o比较两个化合物库的共同化合物;
o查找某批样品中新增或缺失的化合物编号;
o比较不同数据库收录的分子;
o筛选两个实验方案共同使用的溶剂或催化剂;
o判断训练集与测试集是否存在重复样本。
例如:
training_ids = {"cmpd_001", "cmpd_002", "cmpd_003"}
test_ids = {"cmpd_003", "cmpd_004"}
overlap = training_ids & test_ids
print(overlap)
输出为:
{'cmpd_003'}
如果训练集和测试集出现重复化合物,可能导致机器学习模型评价结果过于乐观。因此,在模型构建前检查数据集之间的交集是一项重要的数据质量控制步骤。
(5)四种数据结构的比较
数据结构 | 是否有序 | 是否可修改 | 是否允许重复 | 典型用途 |
列表list | 是 | 是 | 是 | 实验序列、收率列表、样品序列 |
元组tuple | 是 | 否 | 是 | 固定条件、坐标、不可变参数 |
字典dict | 保持插入顺序 | 是 | 键不能重复 | 分子记录、实验记录、参数表 |
集合set | 不用于表达顺序 | 是 | 否 | 去重、成员判断、集合比较 |
可以根据问题类型进行选择:
temperatures = [25, 40, 60, 80]# 一组可扩展的温度数据
condition = (80, "ethanol", "N2")# 一个固定条件组合
compound = {"name": "ethanol", "smiles": "CCO"}# 一条结构化记录
solvents = {"water", "ethanol", "acetone"}# 不重复的溶剂集合
(6)列表、字典与化学数据的组合使用
在实际化学研究中,经常需要组合使用多种数据结构。例如,用列表保存一批化合物,用字典保存每个化合物的详细信息:
compound_library = [
{
"id": "cmpd_001",
"name": "ethanol",
"smiles": "CCO",
"molar_mass": 46.07
},
{
"id": "cmpd_002",
"name": "acetone",
"smiles": "CC(=O)C",
"molar_mass": 58.08
},
{
"id": "cmpd_003",
"name": "acetic acid",
"smiles": "CC(=O)O",
"molar_mass": 60.05
}
]
访问第一个化合物的名称:
print(compound_library[0]["name"])
访问第三个化合物的摩尔质量:
print(compound_library[2]["molar_mass"])
这种结构可以表达“多条记录,每条记录包含多个字段”的数据关系。后续可通过循环对每条记录进行处理,或将其转换为 Pandas DataFrame:
import pandas as pd
compound_df = pd.DataFrame(compound_library)
print(compound_df)
输出将呈现为表格形式,更适合进一步进行筛选、统计和可视化。
5.3.3 条件判断、循环与函数
变量、列表和字典用于保存数据,而条件判断、循环和函数则用于控制程序如何处理这些数据。它们共同构成Python程序的基本逻辑。
在化学研究中,这些结构可用于实现许多重复性任务,例如:
o根据收率、纯度或信噪比对实验结果进行初步分类;
o批量计算一组样品的浓度、产率或分子描述符;
o逐个读取多个仪器输出文件;
o将常用计算公式封装为可重复调用的函数;
o对异常值、缺失值或未收敛计算结果进行标记和处理。
(1)条件判断
条件判断通过if、elif和else实现。程序会先检查if后的条件;若条件为True,则执行对应代码块;若不成立,则继续判断后续elif条件;若所有前述条件都不成立,则执行else对应代码块。
yield_percent = 68.0
if yield_percent >= 80:
print("反应收率较高")
elif yield_percent >= 50:
print("反应收率中等")
else:
print("反应收率较低,需要优化条件")
输出为:
反应收率中等
上述程序的判断逻辑可以表示为:

这里的elif是“else if”的缩写,用于表示“否则,若满足另一个条件”。同一个条件判断结构中可以有多个elif,但通常只包含一个if,并且else为可选部分。
1. Python的缩进规则
Python使用缩进表示代码块,而不像一些编程语言使用大括号{}。通常以4个空格作为一级缩进。
if yield_percent >= 80:
print("反应收率较高")
其中,print()前的4个空格表示该语句属于if条件控制的代码块。
错误的缩进会导致程序报错,或造成与预期不一致的逻辑。例如:
if yield_percent >= 80:
print("反应收率较高")
这段代码会产生缩进错误。
在Jupyter Notebook、Visual Studio Code等编辑器中,按Tab键通常可以自动生成缩进;但在团队协作或正式代码中,建议统一使用4个空格,而不要混用制表符和空格。
2. 条件的顺序
多个条件的顺序十分重要。Python从上到下依次判断,遇到第一个为True的条件后,就执行该分支并跳过后续elif和else。
例如,以下代码的逻辑是正确的:
yield_percent = 85.0
if yield_percent >= 80:
print("高收率")
elif yield_percent >= 50:
print("中等收率")
else:
print("低收率")
输出为:
高收率
如果将条件顺序写反:
yield_percent = 85.0
if yield_percent >= 50:
print("中等或较高收率")
elif yield_percent >= 80:
print("高收率")
程序会首先满足yield_percent >= 50,因此输出:
中等或较高收率
后面的elif yield_percent >= 80 将不会被执行。对于具有范围层级的判断,通常应将更严格或阈值更高的条件放在前面。
3. 使用逻辑运算符组合条件
可以使用and、or和not构建更复杂的条件。
例如,要求反应同时达到收率和纯度的最低标准:
yield_percent = 82.5
purity_percent = 96.8
if yield_percent >= 80 and purity_percent >= 95:
print("该实验结果通过初步质量控制。")
else:
print("该实验结果需要进一步检查。")
输出为:
该实验结果通过初步质量控制。
若希望判断反应温度是否超出设定的操作范围:
temperature_C = 125
if temperature_C < 0 or temperature_C > 100:
print("温度超出预设范围。")
else:
print("温度处于预设范围内。")
使用not可判断某一状态是否不成立:
optimization_converged = False
if not optimization_converged:
print("几何优化尚未收敛,需要检查计算任务。")
4. 判断缺失值或空值
在Python中,判断一个变量是否为None时,推荐使用is None,而不是== None:
melting_point_C = None
if melting_point_C is None:
print("熔点数据尚未记录。")
else:
print(f"熔点为:{melting_point_C} °C")
is用于判断两个对象是否为同一对象;对于None这一特殊单例对象,is None是规范且清晰的写法。
5. 嵌套条件判断
一个条件分支中还可以包含另一个条件判断,称为嵌套条件判断。例如,先检查反应是否达到最低收率,再进一步检查纯度:
yield_percent = 76.0
purity_percent = 93.0
if yield_percent >= 50:
if purity_percent >= 95:
print("收率和纯度均达到要求。")
else:
print("收率合格,但产物纯度需要进一步提高。")
else:
print("收率偏低,建议优先优化反应条件。")
嵌套结构可以表达复杂规则,但层级过多会降低代码可读性。对于较复杂的判断逻辑,常可通过合理的逻辑表达式、独立函数或表格化规则进行简化。
6. 一个简单的实验筛选示例
下面的代码根据转化率和选择性对催化实验进行初步评价:
conversion_percent = 92.0
selectivity_percent = 88.0
if conversion_percent >= 90 and selectivity_percent >= 90:
print("催化性能优良。")
elif conversion_percent >= 80 and selectivity_percent >= 80:
print("催化性能较好,但仍有优化空间。")
else:
print("催化性能有限,建议检查催化剂或反应条件。")
应当注意,代码中的阈值仅为示例。实际研究中,“合格”“优良”或“需要优化”的标准应来自研究目标、文献基准、实验误差和应用要求,而不应由程序任意设定。
(2)循环
循环用于重复执行一段代码。在化学数据处理中,许多任务都具有重复性,例如依次读取多个样品、计算多个化合物的性质、遍历一批实验记录,或逐个检查量子化学任务是否收敛。
Python中常用的循环包括for循环和while循环。对于明确的数据集合或已知次数的重复任务,for循环最为常用。
1. for循环遍历列表
molecules = ["ethanol", "acetone", "benzene"]
for molecule in molecules:
print("正在处理:", molecule)
输出为:
正在处理: ethanol
正在处理: acetone
正在处理: benzene
其中:
·molecules 是待遍历的列表;
·molecule 是循环变量,依次代表列表中的每一个元素;
·缩进的 print() 语句将在每次循环中执行一次。
循环变量名应体现元素的实际含义。对于化合物列表,可使用 molecule 或 compound_name;对于实验结果,可使用 result;对于温度列表,可使用 temperature_C。虽然也可以使用 x 或 i,但含义明确的变量名更便于科研代码的阅读。
2. 遍历数值列表并完成计算
例如,计算多个浓度条件下溶质在固定体积中的物质的量:
python
concentrations_mol_L = [0.01, 0.05, 0.10, 0.20]
volume_L = 0.025
for concentration_mol_L in concentrations_mol_L:
amount_mol = concentration_mol_L * volume_L
print(
f"浓度:{concentration_mol_L:.2f} mol/L,"
f"物质的量:{amount_mol:.5f} mol"
)
输出为:
text
浓度:0.01 mol/L,物质的量:0.00025 mol
浓度:0.05 mol/L,物质的量:0.00125 mol
浓度:0.10 mol/L,物质的量:0.00250 mol
浓度:0.20 mol/L,物质的量:0.00500 mol
这种“逐个读取—计算—输出”的过程,是批量数据分析的基本模式。
3. 使用 range() 生成数值序列
对于已知次数的重复操作,可使用 range() 生成整数序列:
python
for i in range(1, 6):
print("第", i, "次测量")
输出为:
text
第 1 次测量
第 2 次测量
第 3 次测量
第 4 次测量
第 5 次测量
range(start, stop)生成从 start 开始到 stop - 1 结束的整数序列。因此:
python
range(1, 6)
对应:
text
1, 2, 3, 4, 5
常见用法包括:
python
range(5)# 0, 1, 2, 3, 4
range(1, 5)# 1, 2, 3, 4
range(0, 10, 2)# 0, 2, 4, 6, 8
例如,模拟进行三次平行测定的记录提示:
python
for replicate in range(1, 4):
print(f"正在记录第 {replicate} 次平行测定。")
4. 使用enumerate()同时获取索引和元素
当需要同时获得样品序号和样品名称时,可使用enumerate():
samples = ["sample_A", "sample_B", "sample_C"]
for index, sample in enumerate(samples):
print(index, sample)
输出为:
0 sample_A
1 sample_B
2 sample_C
若希望编号从1开始,可指定start=1:
for number, sample in enumerate(samples, start=1):
print(f"样品 {number}: {sample}")
输出为:
样品 1: sample_A
样品 2: sample_B
样品 3: sample_C
5. 同时遍历两组对应数据:zip()
实验数据经常以两组相互对应的列表保存,例如温度和收率。可使用zip()同时遍历:
temperatures_C = [25, 40, 60, 80]
yields_percent = [45.2, 61.8, 78.4, 73.1]
for temperature_C, yield_percent in zip(temperatures_C, yields_percent):
print(f"{temperature_C} °C时,收率为{yield_percent:.1f}%")
输出为:
25 °C 时,收率为 45.2%
40 °C 时,收率为 61.8%
60 °C 时,收率为 78.4%
80 °C 时,收率为 73.1%
使用zip()前,应检查两个列表是否确实一一对应,并且长度一致。若长度不同,zip()通常会按较短列表截断,可能导致部分数据未被处理。
print(len(temperatures_C))
print(len(yields_percent))
对于正式研究数据,使用Pandas DataFrame保存成对或多列数据通常比维护多个并列列表更安全。
6. 遍历字典
字典可用于保存化合物或实验记录。使用.items()可以同时读取键和值:
compound = {
"name": "caffeine",
"molar_mass_g_mol": 194.19,
"logP": -0.07
}
for key, value in compound.items():
print(f"{key}: {value}")
输出为:
name: caffeine
molar_mass_g_mol: 194.19
logP: -0.07
例如,可检查某条实验记录中的字段:
reaction_record = {
"temperature_C": 80,
"time_h": 4.0,
"yield_percent": 82.5,
"solvent": "ethanol"
}
for field, value in reaction_record.items():
print(f"{field}: {value}")
7. 在循环中使用条件判断
循环常与条件判断结合,用于批量筛选数据。例如,筛选收率不低于70%的实验:
yields_percent = [45.2, 61.8, 78.4, 73.1, 85.6]
for yield_percent in yields_percent:
if yield_percent >= 70:
print(f"{yield_percent:.1f}%:保留")
else:
print(f"{yield_percent:.1f}%:暂不优先考虑")
也可对一组量子化学任务状态进行检查:
calculation_status = ["converged", "failed", "converged", "running"]
for status in calculation_status:
if status == "converged":
print("计算已收敛,可以提取结果。")
elif status == "failed":
print("计算失败,需要检查输入文件或错误信息。")
else:
print("计算尚未完成。")
8.break、continue和pass
在部分情况下,需要提前结束循环或跳过某次循环。
break用于立即退出整个循环:
temperatures_C = [25, 40, 60, 80, 100]
for temperature_C in temperatures_C:
if temperature_C > 80:
print("达到预设温度上限,停止处理。")
break
print(f"处理 {temperature_C} °C 的实验数据。")
continue用于跳过当前循环的剩余代码,直接进入下一次循环:
yields_percent = [45.2, None, 78.4, 73.1]
for yield_percent in yields_percent:
if yield_percent is None:
print("发现缺失收率,跳过该记录。")
continue
print(f"当前收率:{yield_percent:.1f}%")
pass表示暂不执行任何操作,常用于代码结构尚未完成时占位:
python
for sample in ["sample_A", "sample_B"]:
pass
在正式科研代码中,不应长期保留没有说明的pass语句,以免遗漏尚未实现的重要处理步骤。
9.while循环
while循环会在某个条件为True时持续执行。它适合循环次数事先不确定、需要根据过程状态决定是否继续的情形。
temperature_C = 25
while temperature_C <= 80:
print(f"当前温度:{temperature_C} °C")
temperature_C = temperature_C + 15
输出为:
当前温度:25 °C
当前温度:40 °C
当前温度:55 °C
当前温度:70 °C
使用while时必须保证循环条件最终会变为False,否则会形成无限循环。例如,以下代码存在问题:
temperature_C = 25
while temperature_C <= 80:
print(temperature_C)
由于temperature_C从未更新,条件始终成立,程序会持续输出25。在Jupyter Notebook中若发生此类情况,可使用“中断内核”停止程序。
在化学数据分析的初学阶段,多数批量任务使用for循环即可。while更适用于实验自动化中的状态监测、迭代优化或持续采集等任务。
(3)函数
函数(function)是将一组可重复使用的操作封装起来的代码单元。函数可以接收输入数据,执行计算或处理步骤,并返回结果。使用函数能够减少重复代码,提高程序的可读性、可维护性和可测试性。
例如,定义一个计算百分产率的函数:
def calculate_yield(actual_mass, theoretical_mass):
"""根据实际质量和理论质量计算百分产率。"""
return actual_mass / theoretical_mass * 100
result = calculate_yield(1.42, 1.80)
print(f"产率为:{result:.1f}%")
输出为:
产率为:78.9%
该函数对应的化学计算关系为:

函数定义的基本结构为:
def function_name(parameter_1, parameter_2):
"""函数说明。"""
# 执行操作
return result
其中:
odef是定义函数的关键字;
ofunction_name是函数名称;
o括号中的内容为函数参数;
o冒号: 表示函数定义开始;
o缩进部分为函数体;
oreturn用于返回计算结果;
o三引号中的文字为文档字符串(docstring),用于说明函数用途。
1. 函数名称与参数命名
函数名称宜体现其用途,参数名称应表达输入量的物理意义和单位。例如:
def calculate_amount_mol(concentration_mol_L, volume_L):
"""根据浓度(mol/L)和体积(L)计算物质的量(mol)。"""
return concentration_mol_L * volume_L
调用函数:
amount_mol = calculate_amount_mol(0.20, 0.050)
print(f"物质的量:{amount_mol:.4f} mol")
输出为:
物质的量:0.0100 mol
相比以下不够明确的写法:
def calculate(x, y):
return x * y
前一种函数更容易被其他研究者理解和正确使用。
2. 位置参数与关键字参数
调用函数时,最常见的是按参数位置传入数值:
result = calculate_yield(1.42, 1.80)
这里1.42对应actual_mass,1.80对应theoretical_mass。
也可以使用关键字参数明确指定各输入值的含义:
result = calculate_yield(
actual_mass=1.42,
theoretical_mass=1.80
)
对于参数较多、单位易混淆的化学函数,使用关键字参数通常更清晰。例如:
amount_mol = calculate_amount_mol(
concentration_mol_L=0.20,
volume_L=0.050
)
3. 默认参数
函数可为部分参数设置默认值。调用函数时,如果不提供该参数,则使用默认值。
def calculate_moles(concentration_mol_L, volume_mL, volume_unit="mL"):
"""根据浓度和体积计算物质的量。"""
if volume_unit == "mL":
volume_L = volume_mL / 1000
elif volume_unit == "L":
volume_L = volume_mL
else:
raise ValueError("volume_unit 必须为 'mL' 或 'L'")
return concentration_mol_L * volume_L
例如:
amount_1 = calculate_moles(0.10, 25)
amount_2 = calculate_moles(0.10, 0.025, volume_unit="L")
print(amount_1)
print(amount_2)
两者都得到:
0.0025
0.0025
此例说明,函数可以通过参数约定减少单位换算错误。但对于复杂或重要的科研计算,更推荐统一使用SI单位或在数据表中单独设置单位字段,而不是依赖过于灵活的默认设置。
4. 返回值与print()的区别
print()用于显示信息,return用于将结果返回给函数外部,以便后续计算。
例如:
def print_yield(actual_mass, theoretical_mass):
yield_percent = actual_mass / theoretical_mass * 100
print(f"产率为:{yield_percent:.1f}%")
调用该函数会显示结果:
print_yield(1.42, 1.80)
但无法方便地将结果继续用于其他计算:
result = print_yield(1.42, 1.80)
print(result)
此时result的值为None,因为函数中没有return。
更好的写法是:
def calculate_yield(actual_mass, theoretical_mass):
"""根据实际质量和理论质量计算百分产率。"""
return actual_mass / theoretical_mass * 100
yield_percent = calculate_yield(1.42, 1.80)
print(f"产率为:{yield_percent:.1f}%")
原则上,计算函数应优先使用return返回结果;是否显示、保存或绘图,则由函数外部的代码决定。这种设计能提高函数的通用性。
5. 一个具有输入检查的函数
科研数据可能存在零值、负值或缺失值。对于重要计算,应在函数中加入必要的输入检查。例如,理论产物质量不能为零或负数:
def calculate_yield(actual_mass_g, theoretical_mass_g):
"""
根据实际产物质量和理论产物质量计算百分产率。
Parameters
----------
actual_mass_g : float
实际获得产物的质量,单位为 g。
theoretical_mass_g : float
理论产物质量,单位为 g,必须大于 0。
Returns
-------
float
百分产率,单位为 %。
"""
if theoretical_mass_g <= 0:
raise ValueError("理论质量必须大于 0 g。")
if actual_mass_g < 0:
raise ValueError("实际质量不能为负值。")
return actual_mass_g / theoretical_mass_g * 100
正常调用:
yield_percent = calculate_yield(1.42, 1.80)
print(f"产率为:{yield_percent:.1f}%")
若错误地输入理论质量为零:
calculate_yield(1.42, 0)
程序将主动报告:
ValueError: 理论质量必须大于0 g。
这种做法不能保证所有输入都符合实验事实,但能够防止明显不合理的数据直接进入计算流程。
6. 返回多个结果
函数可以使用元组一次返回多个值。例如,在计算平均值时,同时返回平均值、最小值和最大值:
def summarize_values(values):
"""返回数据的平均值、最小值和最大值。"""
mean_value = sum(values) / len(values)
min_value = min(values)
max_value = max(values)
return mean_value, min_value, max_value
调用时可通过元组解包接收多个结果:
yields_percent = [45.2, 61.8, 78.4, 73.1, 85.6]
mean_yield, min_yield, max_yield = summarize_values(yields_percent)
print(f"平均收率:{mean_yield:.1f}%")
print(f"最低收率:{min_yield:.1f}%")
print(f"最高收率:{max_yield:.1f}%")
输出为:
平均收率:68.8%
最低收率:45.2%
最高收率:85.6%
但应注意,若输入列表为空,len(values)为零,将导致除零错误。因此,更稳妥的函数应先检查数据是否为空:
def summarize_values(values):
"""返回数据的平均值、最小值和最大值。"""
if len(values) == 0:
raise ValueError("输入数据不能为空。")
mean_value = sum(values) / len(values)
min_value = min(values)
max_value = max(values)
return mean_value, min_value, max_value
7. 函数与循环结合:批量计算
函数特别适合与循环结合,用于批量处理实验数据。例如,计算多组实验的百分产率:
actual_masses_g = [1.42, 1.58, 1.21, 1.67]
theoretical_masses_g = [1.80, 1.80, 1.80, 1.80]
for actual_mass_g, theoretical_mass_g in zip(
actual_masses_g,
theoretical_masses_g
):
yield_percent = calculate_yield(actual_mass_g, theoretical_mass_g)
print(f"实际质量:{actual_mass_g:.2f} g,产率:{yield_percent:.1f}%")
输出为:
实际质量:1.42 g,产率:78.9%
实际质量:1.58 g,产率:87.8%
实际质量:1.21 g,产率:67.2%
实际质量:1.67 g,产率:92.8%
这种模式可以进一步扩展到数百条或数千条实验记录。但当数据规模较大时,通常应优先使用Pandas的向量化计算,而不是逐行编写循环;后续章节将介绍相关方法。
8. 函数文档字符串与类型标注
对于重要科研代码,建议通过文档字符串说明输入、输出、单位、数据类型和适用范围。也可以使用类型标注(type hints)增强代码的可读性。
def calculate_concentration(
amount_mol: float,
volume_L: float
) -> float:
"""
计算物质的量浓度。
Parameters
----------
amount_mol : float
溶质的物质的量,单位为 mol。
volume_L : float
溶液体积,单位为 L,必须大于 0。
Returns
-------
float
溶液浓度,单位为 mol/L。
"""
if volume_L <= 0:
raise ValueError("溶液体积必须大于 0 L。")
return amount_mol / volume_L
调用:
concentration_mol_L = calculate_concentration(
amount_mol=0.0025,
volume_L=0.050
)
print(f"浓度:{concentration_mol_L:.4f} mol/L")
类型标注中的float表示该参数预期为浮点数,->float表示函数预期返回浮点数。Python默认不会仅因类型不匹配而阻止运行,因此类型标注主要用于说明、代码检查和开发工具提示,而不是替代数据验证。
9. 变量的作用域
在函数内部定义的变量通常只在函数内部有效,这称为局部变量。
def calculate_area(radius_cm):
area_cm2 = 3.14159 * radius_cm ** 2
return area_cm2
area = calculate_area(2.0)
print(area)
这里的area_cm2是函数内部的局部变量。在函数外部直接访问:
python
print(area_cm2)
会产生错误,因为该变量并未在函数外部定义。
合理使用局部变量可避免不同计算步骤之间意外覆盖同名变量。对于科研代码,应尽量减少对全局变量的依赖,使函数所需输入通过参数传入,计算结果通过return返回。
下面的示例结合字典、循环、条件判断和函数,对一组反应记录进行初步评价。
reaction_results = [
{
"experiment_id": "RXN_001",
"temperature_C": 40,
"yield_percent": 42.5,
"purity_percent": 91.2
},
{
"experiment_id": "RXN_002",
"temperature_C": 60,
"yield_percent": 76.8,
"purity_percent": 96.4
},
{
"experiment_id": "RXN_003",
"temperature_C": 80,
"yield_percent": 85.3,
"purity_percent": 97.1
}
]
首先定义一个评价函数:
def evaluate_reaction(yield_percent, purity_percent):
"""
根据收率和纯度对反应结果进行初步分类。
Parameters
----------
yield_percent : float
反应收率,单位为 %。
purity_percent : float
产物纯度,单位为 %。
Returns
-------
str
初步评价结果。
"""
if yield_percent >= 80 and purity_percent >= 95:
return "优先条件"
elif yield_percent >= 60 and purity_percent >= 90:
return "可进一步优化"
else:
return "需要重新评估"
然后遍历每一条实验记录:
for record in reaction_results:
evaluation = evaluate_reaction(
yield_percent=record["yield_percent"],
purity_percent=record["purity_percent"]
)
print(
f'{record["experiment_id"]}: '
f'{record["temperature_C"]} °C,'
f'收率 {record["yield_percent"]:.1f}%,'
f'纯度 {record["purity_percent"]:.1f}% → '
f'{evaluation}'
)
输出为:
RXN_001: 40 °C,收率 42.5%,纯度 91.2% → 需要重新评估
RXN_002: 60 °C,收率 76.8%,纯度 96.4% → 可进一步优化
RXN_003: 80 °C,收率 85.3%,纯度 97.1% → 优先条件
这一程序展示了基本的数据处理逻辑:
1.使用列表保存多条反应记录;
2.使用字典保存每条记录的字段;
3.使用函数封装评价规则;
4.使用循环依次处理各实验;
5.使用条件判断进行分类;
6.使用格式化字符串输出结果。
在真实研究中,这种初步分类只能作为辅助决策。是否将某一条件确定为“优先条件”,还应综合考虑重复实验误差、底物适用范围、反应时间、原料成本、分离难度、放大可行性、安全性与绿色化学指标等因素。