013

RDKit Cookbook:化学信息学实战的必备手册

RDKit Cookbook 是化学信息学实战的速查手册。这篇整理最常用的操作与容易踩的坑。

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;许多函数会原地修改分子对象。

延伸资源