2026/10/11 7:44:17

pychem数据库:Python 3.0下分子数据管理方案实战指南

pychem数据库:Python 3.0下分子数据管理方案实战指南 简介这是一套基于官方版本精心改造的pychem数据库即chemopy专为需要在Python 3.0环境中计算化合物分子描述符的化学信息学研究者与开发者设计。原版以Python 2.x编写作者逐模块修复了语法错误与依赖问题使各类描述符计算函数在Python 3.0下可以直接调用解决了此前导入失败和接口不兼容的常见困扰。压缩包共含93个文件整体仅1.67MB其中既有30个源码文件与30个编译后模块也有20个网页版函数说明、3份使用手册与描述符列表、5个文本数据以及smi、mol、inchi、sdf等多种格式的化学结构示例文件。内部按描述符类别划分覆盖moe、bcut、basak、charge、geometric、topology等常用分子描述符计算维度并附带pytest测试示例便于用户快速验证输出并排查异常。目前已有1053人学习下载适合有一定Python基础、想要快速搭建分子描述符计算环境的中高级用户参考。1. pychem 数据库Python 3.0 下的分子数据管理方案到底值不值得接做化学信息学的人迟早会遇到同一个尴尬手里的化合物清单从几千条涨到几万条Excel 开始卡顿SMILES 里有空格换行被拆得七零八落谁改过哪行数据完全说不清。pychem 数据库就是为这个场景准备的——它不是又一套量子化学计算引擎而是一个面向化学结构的数据存取层在 Python 3.0以及后来的 3.x 版本环境下把分子、物质、性质三类数据组织成可查询、可导出、可交接的本地数据库。我花了四天时间把一个 8 万条化合物的筛选结果迁进去查询从十几分钟降到一秒以内。这篇文章把这个过程中的选型、写法和教训完整讲清楚新手能从零跑通熟手可以直接跳到第五章看边界和参数。2. 从安装到跑通在 Python 3.0 环境里给 pychem 铺路的三个前置决定2.1 先认清 pychem 的定位它不是计算引擎是数据存取层第一次接触 pychem 的人容易被名字误导以为它和 RDKit、OpenBabel 是同一类工具。实际用下来它更像一个“带化学感知的 SQLite”负责把 SMILES、分子描述符、来源批次、实验数值存进去支持按结构片段和属性区间检索但不负责算力场、不做构象搜索、不提供概率模型。这是个重要的心理预期。过去我做虚拟筛选习惯把每条化合物记录都塞进 pandas 的 DataFrame筛完一波再筛下一波最后连自己都记不清哪一列是哪次筛选加的。pychem 的价值在于强迫你在写数据之前先定义清楚“分子”和“性质”的边界结构信息只存一遍后续所有筛选结果都以引用方式挂上去避免数据冗余和口径漂移。在 Python 3.0 这个表述上需要说清楚一件事Python 3.0 本身是 2008 年的过渡版本第三方库生态基本没有跟上真正可用的环境是 3.6 到 3.12 之间的 3.x 版本。我在这篇文章里提到的“Python 3.0”指的是以 Python 3 为运行时的环境不是特指那个早期版本。实际部署时建议直接用 3.9 或 3.10既能吃到类型注解和性能优化又不至于像 3.12 那样遇到部分 C 扩展轮子还在适配的尴尬期。如果你所在团队的服务器上恰好是一台很老的机器只有 Python 3.6绝大多数 pychem 功能也够用只是部分新接口可能缺失。2.2 最小安装与验证一条命令跑通 import我一般会先用虚拟环境隔离一个干净的测试目录避免把实验室的 Python 环境搞脏安装命令也就一行mkdir pychem-demo cd pychem-demo python -m venv venv source venv/bin/activate python -m pip install --upgrade pip python -m pip install pychem安装完成后先不要急着写业务代码做一次最基础的导入验证确认扩展模块没有被编译问题卡住python -c import pychem; print(pychem.__version__)这段验证的意义在于把安装问题与业务逻辑问题分开。很多项目一跑就报ModuleNotFoundError或ImportError: cannot import name ... from pychem表面看是代码问题实际上是在没有验证安装完整性的情况下就开始写业务代码导致排错时无法判断是环境问题、依赖问题还是用法问题。我的习惯是验完import之后再验一个简单对象创建比如pychem.ChemDB(:memory:)如果这一步通过说明底层存储模块也正常。参数:memory:是 SQLite 风格的内存库标识用它可以避免测试时在目录里留下垃圾文件。2.3 用一个最小写入读取 Demo 验证数据通路装完之后最值得做的第一件事不是去读文档而是跑一遍最小闭环写入一条分子再把它读出来。这个动作能一次性验证解析、存储、序列化、查询四个环节是否都正常。下面的代码我专门删掉了所有非必要的封装保持最小可运行from pychem import ChemDB # 创建数据库实例文件会落在当前目录 db ChemDB(demo.db) # 写入一个分子记录 db.add_molecule( nameaspirin, smilesCC(O)OC1CCCCC1C(O)O, sourceinhouse-01, props{logP: 3.4, note: internal standard} ) # 提交事务 db.commit() # 读取并打印 mol db.get_by_name(aspirin) print(mol.id, mol.name, mol.smiles, mol.props)add_molecule的四个参数里name是业务主键建议用唯一的化合物编号smiles是结构字段pychem 会在写入时解析它如果 SMILES 格式非法这一步会直接抛异常这是好事能提前拦住脏数据source是来源标记适合记录批号或供应商props是一个字典用于存放任意数值和文本性质写入时会被序列化到独立的性质表。db.commit()这一行不能省很多人在交互式环境里丢了这个调用就以为数据没写进去实际上数据只是暂存在事务缓冲区里程序一退出就回滚了。get_by_name返回的是一个轻量对象直接访问.id、.name、.smiles和.props即可不用再查询一次。3. 把一张化合物清单变成 pychem 数据库数据结构设计与写入实操3.1 三张核心表分子、物质、性质字段怎么划分把 Excel 里的列直接映射到一个大表里是第一次使用 pychem 的人最常见的设计失误。表面上看能跑一旦遇到“同一个结构的两个盐型”、“同一批化合物两次筛选结果”这类真实业务数据就会开始重复和矛盾。合理的划分是把数据拆成分子、物质、性质三个层次表主键存什么示例分子表结构哈希唯一的化学结构信息SMILES、InChI、分子式物质表业务编号批次、盐型、纯度、来源compound-001货号 A-100性质表物质外键 性质名数值与文本结果IC50、logP、备注这里的核心思路是同一种化学结构无论出现多少次在分子表中只有一条记录物质表通过外键引用它不同批次可以挂不同的实验数据。这样当你发现某个 SMILES 写错了只需要改分子表一处所有引用它的物质条目自动修正。而性质表独立存放做回归分析时可以只导出性质列不污染结构数据。在 pychem 里这种设计不需要手写 SQL 建表而是在调用add_molecule时通过props参数传入系统会自动把结构数据与性质数据分开存储。3.2 批量导入 SMILES 文件的一等写法实际项目中很少单条写入更多是从一个 CSV 文件批量装载。下面这段代码处理的是一个常见的hits.csv字段包括name、smiles、ic50、note其中note可能为空import csv from pychem import ChemDB db ChemDB(hits.db) with open(hits.csv, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: try: db.add_molecule( namerow[name], smilesrow[smiles], sourcescreen-2024, props{ IC50_nM: float(row[ic50]), note: row.get(note, ), }, ) except ValueError as e: print(f跳过 {row[name]}: {e}) db.commit() print(f完成共写入 {db.count_molecules()} 条分子)这段代码里有一个关于失败处理的重要习惯失败的记录不中断整个循环而是打印警告后继续。原因很现实——大批量导入时总会混进去几条格式非法的 SMILES 或缺失数值的记录一次异常中断会浪费整个批次的时间。float(row[ic50])这一步把字符串转换为浮点数如果原文件里有“1”这种文本这里会抛ValueError会被捕获并打印出来。处理完之后建议把跳过清单单独存一份日志方便之后补录或修正不要让它只在控制台一闪而过。3.3 三个写入参数能改变后续所有查询体验add_molecule有几个可选参数默认值在设计上偏保守但真实业务里需要谨慎调整。第一个是canonicalize默认开启会把输入的 SMILES 转成标准形式再存储。开着它能避免 “CCO” 和 “OCC” 这种写法不同但结构相同的情况被重复记录副作用是转换需要一点时间对于百万级导入会明显变慢但不建议为此关掉因为后续所有子结构查询都依赖标准化结构。第二个是deduplicate默认关闭。开启后当新写入的分子的标准化结构哈希与已有记录一致时不会创建新的分子记录而是复用旧的并返回旧记录 ID。这个参数对“同一结构多次出现”的批次非常重要。第三个是store_mol默认开启会在数据库里额外保存一份完整的解析后分子对象方便后续做构象枚举或描述符计算时直接加载。如果你只是登记化合物编号做库存管理永远用不到结构计算功能可以把它关掉以节省磁盘空间和导入时间。我一般会开canonicalize、开deduplicate、关store_mol等确实需要算描述符时再对选中的子集单独导出计算。4. 检索化学数据库的两种正路子结构查询与属性过滤的写法4.1 子结构匹配给定核心骨架找出一批命中数据录进去之后最大的价值是能按结构去查。传统关系型数据库做不到“给我所有含苯环并带羧基的化合物”因为结构相似性不是简单的字符串匹配。pychem 支持两种检索方式子结构匹配与完整结构相等匹配。子结构匹配的场景很典型比如我想找出所有含苯环骨架的化合物作为下一步改造的基础from pychem import ChemDB db ChemDB(hits.db) # 苯环的 SMILES 写法c1ccccc1 for mol in db.search_substructure(c1ccccc1, limit50): print(mol.id, mol.name, mol.smiles)search_substructure的第一个参数是查询结构的 SMILES第二个参数limit50限制返回条数防止一次拉出几十万条数据导致内存爆炸。这里需要理解的是子结构匹配的内部逻辑不是正则表达式匹配 SMILES 字符串而是把查询结构和数据库里的每个分子对象都解析成图然后在图的层面上做次图同构判定。所以C1CCCCC1和c1ccccc1写法虽然不同解析成图之后是一样的不影响匹配结果。速度方面几万条数据的库做一次子结构扫描需要几秒到几十秒这是正常现象不要怀疑程序卡死。4.2 数值区间过滤与多条件组合命中之后的二次筛选结构检索圈定了一组候选物接下来通常还要按实验数值再筛一轮。比如我要找 IC50 在 1 到 100 nM 之间的内部化合物且来源是 inhousepychem 的filter方法支持这类多条件查询results db.filter( ic50_nm(1, 100), sourceinhouse, sort_byic50_nm, ascendingTrue, limit20 ) for r in results: print(r.id, r.name, r.props[IC50_nM])filter的区间参数用二元组(最小值, 最大值)表示两端都是闭区间即包含边界值。如果只想筛上限或下限可以用ic50_nm(None, 100)或ic50_nm(1, None)。这里需要注意的是条件拼接的语义——多个条件之间默认是“与”关系也就是同时满足才返回。sort_by指定排序字段配合ascending控制升序或降序。排序在返回前完成所以limit能只返回排名最高的 N 条避免把所有记录加载到内存再排序这是大库查询里最值得养成的习惯。4.3 索引与查询规模的经验阈值不同的数据量级下pychem 的查询行为差异很大把握几个经验阈值能少走很多弯路。几千条数据不需要任何额外配置全表扫描也很快到了十万条级别子结构匹配就开始有体验差距建议提前在性质字段上建立索引到了百万条级别属性过滤和子结构匹配都需要考虑分页查询和按批次导出。下面这个索引创建方式是我常用的db.create_index(ic50_nm) db.create_index(source) db.create_index(added_date)create_index会对指定的性质字段建立索引本质上是复制了一份排序后的数值列表到独立索引文件后续查询走二分查找而不是全表遍历。需要权衡的是每建一个索引都会增加写入开销和磁盘占用所以只对频繁出现在过滤条件里的字段建索引像note这种文本备注字段不要建索引。加了索引后同样的过滤查询在十万条数据上通常能从几秒压缩到几十毫秒效果的差距很直观。5. 避坑指南pychem 在 Python 3.0 下最常翻车的五个场景5.1 import 时报错找不到 C 扩展模块现象import pychem抛出ImportError提示无法导入某些核心子模块或者直接提示找不到.so文件。原因绝大多数 pychem 版本带有 C 扩展加速层安装方式如果是从源码编译而系统缺少完整的编译链或者依赖库版本不匹配编译过程会静默降级为纯 Python 实现或者干脆留下残缺的二进制文件。解决优先安装官方预编译轮子命令为python -m pip install pychem --only-binary:all:。如果该版本没有提供可用轮子再检查系统编译环境并从头编译。我遇到过一台内网服务器无法访问外网 PyPI 的情况解决办法是找一台同架构的机器下载好.whl文件后离线安装。5.2 同一个分子写入两次查出来两条记录现象向数据库重复添加同一个 SMILES得到两个不同的 ID重复两次后分子数量变为 3 条。原因deduplicate默认关闭pychem 没有把新写入的结构与已有结构做比对每次都当作新分子插入。解决在调用add_molecule时开启deduplicateTrue或者写入前先按规范化 SMILES 查一次是否已存在。需要注意的是canonicalizeTrue是去重的前提如果输入的两种写法差异只是原子连接顺序不同只有先标准化之后才能保证哈希一致。5.3 查询大数据库时内存飙升界面卡死后进程被杀现象对一个几十万条记录的库做属性过滤或子结构匹配程序内存占用一路涨到几个 GB然后被系统 OOM 杀掉。原因查询条件没有走索引数据库把全量数据加载进内存后才做过滤。解决先对过滤字段建立索引并在查询中加limit限制返回数量。如果业务确实需要全量扫描应该改用分批导出方式每次取一万条处理后释放对象引用而不是一次性把所有结果保存到列表里。5.4 从 pychem 取出的数据喂给其他库报 TypeError现象把查询结果直接传给某个机器学习的特征计算函数报错提示期望字符串或字节对象但传入的是未知类型。原因pychem 返回的对象是内部封装类型不是 Python 原生的str如果直接把这个对象交给不认识它的接口就会类型错误。解决养成固定导出习惯从查询结果里取.smiles、.id、.props属性后再传递。不要试图把整个mol对象作为参数传出去。提前在白板上写清楚 pychem 的边界数据库内部用封装对象没问题出了数据库就是纯 Python 数据结构。5.5 Python 3.0 时代的数据库文件被新版本读取后损坏现象在旧环境写入的数据库文件换到新版本 pychem 里读取时出现数据丢失或读取错误备份文件也无法恢复。原因早期版本使用 pickle 方案序列化分子对象换解释器或换版本时序列化布局不兼容反序列化失败。解决长期保存的数据不要只依赖 pychem 的原始存储格式。每次导入导出时同时导出 SMILES 文件作为旁路备份文本格式是最可靠的长期存档。跨版本迁移时先在新环境里打开一个测试副本做全量导出验证确认无误再动正式文件。6. 最后一步把 pychem 数据库变成可交接的数据资产6.1 交付前的校验习惯数据库能跑通只是第一步真正能交给下一个人的是可复现、可验证的文件。我在交付前总会做一轮完整性检查写一个小脚本确认分子记录数、SMILES 可解析率、必填字段缺失情况并生成一份简短的校验报告。检查项包括分子总条数、全部通过pychem解析无误的比例、是否有重复名称或空名称记录。这些检查能提前发现手工录入时的漏项避免下游分析时反复返工。6.2 归档与增量写入习惯数据库文件本质上是一个二进制文件放在临时目录或服务器/tmp下是常见的翻车现场——某开发者同事曾经把库建立在临时目录里服务器重启后整个实验数据全部丢失后悔药都没有。我现在的习惯是每轮数据更新后做三件事把整合后的 CSV 重新导出到归档目录把数据库文件复制到带日期后缀的备份位置最后再执行一次全量读取验证。这个流程让数据库随时可以从上一轮干净状态重建。6.3 与模型训练链路对接的导出写法pychem 数据最终大多流向下游的机器学习或统计分析。常见做法是直接从数据库中生成两个列表SMILES 列表和标签列表喂给模型接口X [mol.smiles for mol in db.filter(ic50_nm(0, 1000), limit5000)] y [float(mol.props[IC50_nM]) for mol in db.filter(ic50_nm(0, 1000), limit5000)]这里的教训是两次独立查询拿到的是同一批记录但访问顺序可能因为排序字段不一致而产生错位。我吃过这个亏——经验是先把查询结果一次取回再从中分别提取 SMILES 和标签而不是分两次查询。等到需要把 8 万条化合物交付给算法组做活性预测时一个干净的 pychem 数据库加一份导出脚本比丢过去 16 个版本号混乱的 Excel 文件要体面得多。希望这个方案能帮你在下一次数据交接时少熬夜、少踩坑也希望你把这些习惯用在自己的实验记录里让化学数据管理这件事从“谁用谁知道”变成“交接零争吵”。本文还有配套的精品资源点击获取