RDKit 是化学信息学的事实标准工具,而 Cookbook 是它最实用的部分:按「我要做什么」组织的代码片段,而非按 API 组织的文档。这篇整理其中最高频的操作与常见陷阱。
最常用的操作速查
from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors, Draw, rdFingerprintGenerator
from rdkit import DataStructs
# ---- 读写 ----
mol = Chem.MolFromSmiles("CC(=O)Nc1ccc(O)cc1")
smi = Chem.MolToSmiles(mol) # 【规范 SMILES】
mol = Chem.MolFromMolFile("molecule.sdf")
suppl = Chem.SDMolSupplier("molecules.sdf") # 迭代器,省内存
# 【必做】:检查解析是否成功
if mol is None:
print("解析失败")
# ---- 分子性质 ----
Descriptors.MolWt(mol)
Descriptors.MolLogP(mol)
Descriptors.TPSA(mol)
Descriptors.NumHDonors(mol)
Descriptors.NumRotatableBonds(mol)
# 批量算所有描述符
from rdkit.Chem import Descriptors
calc = Descriptors.CalcMolDescriptors(mol) # 返回 dict
# ---- 指纹与相似性 ----
gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
fp1 = gen.GetFingerprint(mol1)
fp2 = gen.GetFingerprint(mol2)
sim = DataStructs.TanimotoSimilarity(fp1, fp2)
# 批量相似性(比循环快得多)
sims = DataStructs.BulkTanimotoSimilarity(fp1, [fp2, fp3, fp4])
# ---- 子结构搜索 ----
patt = Chem.MolFromSmarts("c1ccccc1")
mol.HasSubstructMatch(patt)
mol.GetSubstructMatches(patt)
# ---- 三维构象 ----
mol = Chem.AddHs(mol) # 【必须先加氢】
params = AllChem.ETKDGv3()
params.randomSeed = 0xf00d # 【固定种子保证可复现】
AllChem.EmbedMolecule(mol, params)
AllChem.MMFFOptimizeMolecule(mol)
# 多构象
cids = AllChem.EmbedMultipleConfs(mol, numConfs=50, params=params)
AllChem.MMFFOptimizeMoleculeConfs(mol)
# ---- 骨架 ----
from rdkit.Chem.Scaffolds import MurckoScaffold
scaffold = MurckoScaffold.GetScaffoldForMol(mol)
scaffold_smi = MurckoScaffold.MurckoScaffoldSmiles(mol=mol)
# ---- 绘图 ----
Draw.MolToImage(mol, size=(400, 400))
Draw.MolsToGridImage([mol1, mol2, mol3], molsPerRow=3,
legends=["A", "B", "C"])
最容易踩的坑
# 【坑一】:忘记检查 MolFromSmiles 返回 None
# 无效的 SMILES 会静默返回 None,
# 后续操作会抛出难以理解的错误
mols = [Chem.MolFromSmiles(s) for s in smiles_list]
valid = [(s, m) for s, m in zip(smiles_list, mols) if m is not None]
print(f"【{len(smiles_list) - len(valid)} 个结构解析失败】")
# 【坑二】:加氢与三维嵌入的顺序
# 【必须先 AddHs 再 EmbedMolecule】
# 反过来会导致氢原子位置错误
mol = Chem.AddHs(mol) # 先
AllChem.EmbedMolecule(mol) # 后
# 分析完可以 RemoveHs
# 【坑三】:不固定随机种子
# ETKDG 有随机性 → 结果不可复现
params = AllChem.ETKDGv3()
params.randomSeed = 0xf00d # 【必须】
# 【坑四】:SMILES 的规范化
# 同一分子的不同写法直接比较字符串会失败
smi1 = Chem.MolToSmiles(Chem.MolFromSmiles("c1ccccc1"))
smi2 = Chem.MolToSmiles(Chem.MolFromSmiles("C1=CC=CC=C1"))
assert smi1 == smi2 # 【规范化后才相等】
# 【坑五】:分子对象被修改
# 很多 RDKit 函数会【原地修改】分子
# → 需要保留原始时先复制
mol_copy = Chem.Mol(mol)
# 【坑六】:从 PDB 读配体时键级丢失
# PDB 格式不含键级信息
# → 用模板恢复
from rdkit.Chem import AllChem
template = Chem.MolFromSmiles("CC(=O)Nc1ccc(O)cc1")
pdb_mol = Chem.MolFromPDBFile("ligand.pdb")
fixed = AllChem.AssignBondOrdersFromTemplate(template, pdb_mol)
# 【坑七】:立体化学在处理中丢失
# 某些操作会清除手性信息
# → 处理后检查
print(Chem.FindMolChiralCenters(mol, useLegacyImplementation=False))
# 【坑八】:性能问题
# 在循环中重复创建 fingerprint generator
# → 应该创建一次重复使用
gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048) # 循环外
高频的实用配方
# ---- 配方一:批量处理带错误处理 ----
from rdkit import RDLogger
RDLogger.DisableLog("rdApp.*") # 关掉恼人的警告
def safe_process(smiles_list, func):
results, failed = [], []
for i, smi in enumerate(smiles_list):
mol = Chem.MolFromSmiles(smi)
if mol is None:
failed.append((i, smi, "解析失败"))
continue
try:
results.append((i, func(mol)))
except Exception as e:
failed.append((i, smi, str(e)))
if failed:
print(f"【{len(failed)} 个失败】,前 3 个: {failed[:3]}")
return results, failed
# ---- 配方二:分子标准化(见 046)----
from rdkit.Chem.MolStandardize import rdMolStandardize
def standardize(smiles):
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return None
mol = rdMolStandardize.Cleanup(mol) # 基本清理
mol = rdMolStandardize.FragmentParent(mol) # 【去盐/去溶剂】
mol = rdMolStandardize.Uncharger().uncharge(mol) # 中和电荷
te = rdMolStandardize.TautomerEnumerator()
mol = te.Canonicalize(mol) # 【规范互变异构体】
return Chem.MolToSmiles(mol)
# ---- 配方三:高亮子结构 ----
def highlight_match(mol, smarts):
patt = Chem.MolFromSmarts(smarts)
hit_atoms = mol.GetSubstructMatch(patt)
hit_bonds = []
for bond in patt.GetBonds():
a1 = hit_atoms[bond.GetBeginAtomIdx()]
a2 = hit_atoms[bond.GetEndAtomIdx()]
hit_bonds.append(mol.GetBondBetweenAtoms(a1, a2).GetIdx())
return Draw.MolToImage(mol, highlightAtoms=hit_atoms,
highlightBonds=hit_bonds, size=(400, 400))
# ---- 配方四:Butina 聚类(见 050)----
from rdkit.ML.Cluster import Butina
def cluster_molecules(mols, cutoff=0.35):
fps = [gen.GetFingerprint(m) for m in mols]
dists = []
for i in range(1, len(fps)):
sims = DataStructs.BulkTanimotoSimilarity(fps[i], fps[:i])
dists.extend([1 - s for s in sims])
return Butina.ClusterData(dists, len(fps), cutoff, isDistData=True)
# ---- 配方五:反应处理 ----
rxn = AllChem.ReactionFromSmarts(
"[C:1](=[O:2])[OH].[N:3]>>[C:1](=[O:2])[N:3]") # 酰胺化
products = rxn.RunReactants((acid_mol, amine_mol))
for p in products:
Chem.SanitizeMol(p[0]) # 【产物必须 sanitize】
print(Chem.MolToSmiles(p[0]))
性能优化
| 做法 | 效果 |
|---|---|
用 BulkTanimotoSimilarity |
比循环快很多 |
| 复用 generator 对象 | 避免重复初始化 |
用 SmilesMolSupplier 迭代 |
大文件不占内存 |
| 用 multiprocessing 并行 | 指纹计算等 CPU 密集操作 |
| 关闭日志 | RDLogger.DisableLog |
| 缓存计算结果 | 指纹、描述符算一次存起来 |
| 用 Datamol 封装 | 并行与常用流程的便利封装(见 055《化学信息学工具栈》) |
关键要点
- 必须检查 MolFromSmiles 返回 None——无效结构会静默失败;
- 先 AddHs 再 EmbedMolecule,且必须固定随机种子保证可复现;
- 从 PDB 读配体要用模板恢复键级,否则化学结构可能错误;
- 比较分子必须用规范 SMILES;许多函数会原地修改分子对象。
延伸资源
- RDKit 工具:171《RDKit》;工具栈配合:055《化学信息学工具栈》;
- 分子标准化:046《分子标准化》;构象生成:049《构象生成入门》;TeachOpenCADD:012《TeachOpenCADD 教程》。