4. RDKit 入门:环境搭建、分子对象创建、原子与键操作、分子可视化

进入这一章,咱们终于要开始实操了。RDKit 是化学信息学领域绕不开的核心库,几乎所有分子表示学习项目都会用到它。
我个人习惯把它当作「分子瑞士军刀」——从解析 SMILES 到画结构图,从原子遍历到指纹计算,一把刀全搞定。
今天我们先打好基础,把环境配好、对象建好、原子键玩转、最后把分子画出来。

4.1 环境搭建

先说说安装。RDKit 支持 pip 和 conda 两种方式,我强烈推荐 conda,尤其 Windows 用户。
为什么呢?因为 pip 安装的 RDKit 在 Windows 下有时候会和底层库冲突,我以前踩过这个坑——pip install rdkit-pypi 之后,分子可视化死活打不开。
后来换成 conda,一行命令搞定,再也没出过问题。

最简单的安装方式:

conda create -n rdkit_env python=3.10
conda activate rdkit_env
conda install -c conda-forge rdkit

如果你非要用 pip,记得确认你的 Python 版本和系统架构:

pip install rdkit-pypi

注意: pip 版 RDKit 在某些 Linux 发行版上可能缺少图形后端,导致 Draw 模块崩溃。我曾经在生产环境上遇到类似问题,最后乖乖切回 conda。

安装好之后,验证一下:

import rdkit
print(rdkit.__version__)

输出版本号,比如 2024.09.1,就说明环境搭好了。

4.2 分子对象创建

RDKit 里最核心的概念就是 Mol 对象。说白了,一个 Mol 对象就是分子在内存中的完整表示——包含原子、键、立体化学、电荷等等。

4.2.1 从 SMILES 创建

最常用的方法:

from rdkit import Chem

smiles = "CCO"  # 乙醇
mol = Chem.MolFromSmiles(smiles)
print(mol.GetNumAtoms())  # 输出 3

你看,一行就把字符串变成分子了。但要注意,Chem.MolFromSmiles 在遇到非法 SMILES 时会返回 None,所以要加个判断:

mol = Chem.MolFromSmiles("CCZ")  # 非法字符串
if mol is None:
    print("无效 SMILES,请检查")

小经验: 我在做数据清洗时,习惯用 try-except 包一层,因为有些 SMILES 虽然合法但 RDKit 解析会抛出异常(比如超长分子)。

4.2.2 从文件创建

如果手里有 mol 或 sdf 文件:

mol = Chem.MolFromMolFile("ethanol.mol")
if mol:
    print("文件读取成功")

# 读取多个分子的 SDF
suppl = Chem.SDMolSupplier("compounds.sdf")
for mol in suppl:
    if mol is not None:
        print(mol.GetNumAtoms())

这里有个坑:SDMolSupplier 是惰性加载的,遍历时有可能某个分子解析失败返回 None。我刚学的时候没注意,直接用了 mol.GetNumAtoms() 就报错了……

4.2.3 多分子批量读取

实际项目里经常要处理成千上万个分子。你想想看,一个个读太慢了,RDKit 提供了高效方式:

smiles_list = ["CCO", "CCCO", "C1CCCCC1"]
mols = [Chem.MolFromSmiles(smi) for smi in smiles_list]
mols = [m for m in mols if m is not None]  # 过滤无效

或者用 MolFromSmarts 解析 SMARTS 模式(用于子结构匹配),用法类似:

pattern = Chem.MolFromSmarts("[#6]=O")  # 羰基碳

4.3 原子与键操作

拿到 Mol 对象后,你最想干的事就是「拆」它——遍历每个原子、每条键,提取信息。这部分在分子表示学习中非常常用。

4.3.1 原子属性

遍历原子,获取元素符号、原子序数、形式电荷、杂化方式等:

mol = Chem.MolFromSmiles("CCO")
for atom in mol.GetAtoms():
    print(f"原子索引: {atom.GetIdx()}, 元素: {atom.GetSymbol()}, "
          f"原子序数: {atom.GetAtomicNum()}, 电荷: {atom.GetFormalCharge()}, "
          f"度: {atom.GetDegree()}")

输出示例:

原子索引: 0, 元素: C, 原子序数: 6, 电荷: 0, 度: 2
原子索引: 1, 元素: C, 原子序数: 6, 电荷: 0, 度: 3
原子索引: 2, 元素: O, 原子序数: 8, 电荷: 0, 度: 1

还有一个我经常用的属性:GetIsAromatic() 判断是否是芳香原子。

4.3.2 键属性

键的操作类似:

for bond in mol.GetBonds():
    print(f"键索引: {bond.GetIdx()}, 起始原子: {bond.GetBeginAtomIdx()}, "
          f"结束原子: {bond.GetEndAtomIdx()}, 键型: {bond.GetBondTypeAsDouble()}, "
          f"是否芳香: {bond.GetIsAromatic()}")

键型用浮点数表示:1.0 单键,2.0 双键,1.5 芳香键……

属性方法 返回值 说明
GetIdx() int 原子或键的索引(从0开始)
GetSymbol() str 元素符号(仅原子)
GetAtomicNum() int 原子序数
GetFormalCharge() int 形式电荷
GetDegree() int 连接的键数(包括氢?默认不包含)
GetIsAromatic() bool 是否芳香
GetBondTypeAsDouble() float 键级(仅键)

核心要点: 当你需要将分子结构转化为特征时,原子和键的迭代是基础操作。我在做图神经网络(GNN)数据预处理时,就是用这种方式构建分子图的节点和边特征。

4.3.3 修改分子

RDKit 也支持修改分子,比如加氢、删除氢:

mol_h = Chem.AddHs(mol)     # 加显式氢
print(mol_h.GetNumAtoms())  # 原来3个原子,加氢后变9个

mol_noH = Chem.RemoveHs(mol_h)  # 去掉氢
print(mol_noH.GetNumAtoms())    # 回到3

注意:加氢后原子序数会变,迭代时要小心索引错乱。

4.4 分子可视化

你可能想亲眼看看分子长什么样。RDKit 的 Draw 模块提供了非常方便的绘图工具。

4.4.1 2D 结构图

最简单的:

from rdkit.Chem import Draw
from rdkit import Chem

mol = Chem.MolFromSmiles("C1CCCCC1C(=O)O")  # 环己甲酸
img = Draw.MolToImage(mol, size=(300, 300))
img.show()  # Jupyter 里直接显示

如果你在 Jupyter 里,也可以直接用 IPython 的 display:

from IPython.display import display
display(img)

多个分子一起画:

smiles = ["CCO", "CCCO", "C1CCCCC1"]
mols = [Chem.MolFromSmiles(s) for s in smiles]
legends = ["乙醇", "丙醇", "环己烷"]
img_grid = Draw.MolsToGridImage(mols, molsPerRow=3, subImgSize=(200,200), legends=legends)
display(img_grid)

4.4.2 SVG 导出

如果你要把分子图嵌入网页或生成矢量图,用 SVG 格式更合适:

svg_text = Draw.MolToSVG(mol, size=(300, 300))
with open("molecule.svg", "w") as f:
    f.write(svg_text)

SVG 可以直接在浏览器或 Markdown 里显示。

踩坑提醒: 我在 Jupyter Notebook 中直接显示 SVG 时,有时会因为单元格输出过长而卡死。建议用 IPython.display.SVG 包装一下:

from IPython.display import SVG
display(SVG(svg_text))

4.4.3 3D 可视化(了解即可)

RDKit 本身不带 3D 渲染引擎,但可以通过 py3Dmol 或 nglview 配合使用。这里简单提一下:

from rdkit.Chem import AllChem
mol_3d = Chem.MolFromSmiles("CCO")
mol_3d = Chem.AddHs(mol_3d)
AllChem.EmbedMolecule(mol_3d, AllChem.ETKDG())  # 生成3D构象
# 然后用 py3Dmol 显示,这里不展开

4.5 本章知识体系

下面这张图概括了 RDKit 入门操作的核心流程,从字符串到可视化的完整链条:

RDKit 分子操作流程图 SMILES 字符串 "CCO" / 文件读取 Mol 对象 Chem.MolFromSmiles() 原子与键遍历 GetAtoms() / GetBonds() 可视化 Draw.MolToImage() 修饰操作 AddHs / RemoveHs 属性提取 GetSymbol / GetAtomicNum 注意:Mol对象 失败时返回 None, 记得判空 可视化支持多种 输出:PNG、SVG、 以及网格图 RDKit 分子处理全流程:从字符串到可视化

图中展示了本章覆盖的核心路径:SMILES → Mol → 原子/键遍历 → 属性提取 → 修饰 → 可视化。每一步都对应实际代码,形成闭环。

4.6 一些实用技巧

  • 缓存分子对象: 如果你多次用到同一个分子,最好把它存下来,而不是每次从 SMILES 解析。我有个项目解析了10万次同一个 SMILES,后来改成缓存,速度提升了几十倍。
  • 处理大分子: 对于超过1000个原子的分子(比如蛋白质片段),遍历原子键时要小心性能。可以用 mol.GetNumAtoms(onlyExplicit=True) 限制只获取显式原子(默认会包含隐式氢)。
  • 检查芳香性: 很多性质计算(如 logP)需要分子预先进行芳香性感知。推荐统一用 Chem.SanitizeMol(mol) 对分子进行标准化。

个人经验: 做分子图神经网络时,我通常会先 Chem.AddHs(mol) 加氢,再把所有显式原子和键作为图节点和边。这样能更好地捕捉氢键信息,模型效果有明显提升。