第3章 RDKit简介:安装与基本使用,从SMILES到分子对象的转换方法

为什么是RDKit?

做ADMET预测,绕不开分子描述符。而描述符的源头,是分子结构。

RDKit就是那把钥匙。它能把一串字符变成活生生的分子对象,然后你才能在上面做各种计算。

我个人习惯把RDKit当成化学界的“瑞士军刀”。切分子、算指纹、画结构图,几乎全用它。

嗯,记住一点:RDKit是开源库,社区活跃,更新快。我用了快六年,没遇到过修不了的bug。

怎么装?

安装其实不难。但不同系统有些小坑,我踩过几次,给你总结一下。

最推荐的方式:conda

conda装RDKit最稳。它会把依赖的numpy、boost什么的都处理好。

conda install -c conda-forge rdkit

如果你有虚拟环境,建议先建一个干净的再装:

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

备用方式:pip

pip也能装,但依赖容易出问题。

pip install rdkit-pypi
注意:pip版的RDKit有些功能被阉割了,比如部分3D构象生成和某些指纹算法。我建议能用conda就别用pip。

验证安装

装好后,跑一下这行命令:

python -c "from rdkit import Chem; print(Chem.__version__)"

如果打印出版本号,就说明装好了。

小经验:我第一次装RDKit用的是pip,结果跑分子图时一直报段错误。换成conda后一次通过。你如果遇到类似问题,换conda试试。

从SMILES到分子对象

说白了,就是把字符串变成计算机能理解的结构。

基础用法

核心函数是Chem.MolFromSmiles()。它干的事很简单:读入SMILES,返回一个rdkit.Chem.rdchem.Mol对象。

from rdkit import Chem

# 最简单的SMILES
smiles = "CCO"  # 乙醇
mol = Chem.MolFromSmiles(smiles)

print(mol)       # 会打印对象信息
print(type(mol)) # <class 'rdkit.Chem.rdchem.Mol'>

如果SMILES不合法,函数会返回None:

bad_smiles = "XYZ123"
mol = Chem.MolFromSmiles(bad_smiles)
print(mol)  # None

为什么会这样?因为RDKit内部用了一套严格的原子连接规则。你给个乱码,它不会猜,直接返回空。

我在项目中遇到过一个奇怪现象,一个看起来对的SMILES总是返回None。后来发现里面多了一个空格。RDKit对空格和特殊符号很敏感,这也是常见坑。

常用原子操作

拿到分子对象后,你可以做很多事。

  • 获取原子数:mol.GetNumAtoms()
  • 获取重原子数:mol.GetNumHeavyAtoms()
  • 获取分子式:Chem.MolToSmiles(mol) (规范化的SMILES) 或rdMolDescriptors.CalcMolFormula(mol)
  • 查看原子列表:[atom.GetSymbol() for atom in mol.GetAtoms()]
# 举个例子
mol = Chem.MolFromSmiles("c1ccccc1")  # 苯
atoms = [atom.GetSymbol() for atom in mol.GetAtoms()]
print(atoms)  # ['C', 'C', 'C', 'C', 'C', 'C']
print(mol.GetNumAtoms())  # 6
核心思路:所有ADMET描述符计算,都在这步之后。SMILES转换是起点,也是关键。

核心流程:SMILES→描述符的完整路径

为了让整个流程更清晰,我画了一张图。你看着它,就能明白每一步在干什么。

SMILES到分子描述符的完整流程 SMILES 字符串输入 RDKit 解析并校验合法性 无效SMILES 返回 None 合法SMILES rdkit Mol 对象构建 ADMET 描述符计算(LogP、MW、TPSA 等)

有了这张图,你应该能理解:SMILES输入→解析校验→分子对象→描述符计算是一条直线。

中间任何一个环节断了(比如返回None),后面都跑不下去。

常用检查与避坑指南

这里是我实际项目里遇到过的一些问题,你提前知道,能省很多时间。

场景 问题 解决方法
SMILES格式不标准 含有空格、制表符、换行符 用strip()清洗一下
手性问题 SMILES没标注手性,但分子有手性 使用Chem.MolFromSmiles(smiles, sanitize=True)自动处理
多个分子在一个SMILES里 如“CCO.O”表示乙醇和水 用Chem.MolFromSmiles(smiles)只生成一个分子,但会警告。批量处理需拆开
SMILES含有盐或金属 如“CCO.[Na+]” RDKit会识别,但需要额外处理盐
我曾经踩过一个坑:一个包含几百个分子的数据集,跑描述符时突然报错。逐行排查发现,其中一个SMILES字符串末尾有个肉眼看不到的\u200b(零宽空格)。用strip()根本去不掉,得用replace('\u200b', '')。这种隐形字符是最难查的。
建议:每次读取SMILES后,先做一次简单的清洗。
# 清洗SMILES
def clean_smiles(smiles):
    return smiles.strip().replace('\u200b', '').replace('\xa0', ' ')

smiles = " CCO\u200b"
print(repr(clean_smiles(smiles)))  # 'CCO'

多分子处理的批量化

做ADMET特征工程,经常要处理成千上万个分子。一个一个写SMILES肯定不行。

批量转换的写法:

from rdkit import Chem

smiles_list = ["CCO", "c1ccccc1", "CC(=O)O", "C1CCCCC1"]
mol_list = []

for s in smiles_list:
    mol = Chem.MolFromSmiles(s)
    if mol is not None:
        mol_list.append(mol)
    else:
        print(f"警告: SMILES {s} 转换失败")

print(f"成功转换 {len(mol_list)}/{len(smiles_list)} 个分子")

这个循环逻辑很基础,但非常实用。我个人习惯在转换的同时,顺手存一份mol对象和对应的smiles到一个字典里,方便后面回溯。

最后一个小提醒

RDKit的Mol对象很轻量,但不要同时存太多。我在处理百万级分子时,按批次转换并立即释放内存。

为什么要这样做?你想想看,每个对象内部都保存了原子坐标、键连关系,但很多时候你只需要数值描述符。处理完就扔掉,不要抱着对象占内存。

嗯,关于安装和基本转换,今天就到这里。下次你拿到每一条SMILES,都应该先走一遍MolFromSmiles检查一下。这个习惯,能帮你躲开至少一半的坑。