文件读取与文本处理基础
做 MolProbity 报告批量解析,说白了就是跟一堆文本文件打交道。你拿到的每条 PDB、每个 log,本质上都是字符串。怎么读、怎么拆、怎么避免乱码——这几步基本功不扎实,后面所有自动化都会翻车。我个人习惯在项目开始时先花 10 分钟把「读文件」这一层封装好,后面能省掉大量调试时间。
open() 函数与文件模式
Python 内置的 open() 是打开文件的唯一入口。它的第一个参数是路径,第二个参数是模式字符串。我见过不少新手上来就写 open('file.txt'),默认是只读文本模式,没问题。但一旦要写二进制或追加内容,就懵了。
| 模式 | 含义 | 常用场景 |
|---|---|---|
'r' | 只读(文本) | 读取报告文件、配置文件 |
'w' | 写入(覆盖) | 写解析结果、生成新报告 |
'a' | 追加 | 日志聚合、记录错误 |
'rb' | 只读二进制 | 读取 PDB 中的非 ASCII 数据 |
'r+' | 读写 | 原地修改文件(不常用) |
'r' 就够了,结果里面混了 UTF-16 BOM,读出来全是乱码。后来养成习惯:先确认文件原始编码,再选模式。
使用 with 语句管理资源
如果你还在写 f = open(...); data = f.read(); f.close(),我建议你立刻改掉。手动 close() 很容易漏掉——尤其是在异常提前退出时,文件句柄会一直占着。
with 语句是上下文管理器,离开缩进块后自动关闭文件。这不仅干净,而且安全。
with open('report.log', 'r', encoding='utf-8') as f:
content = f.read()
# 出了缩进,文件自动关闭
你想想看,批量解析几百个文件时,如果一个文件没关好,系统打开文件数上限就爆了。用 with 是零成本的习惯。
contextlib.ExitStack。不过大部分场景一个 with 就够了。
按行读取——从内存友好到效率
MolProbity 报告可能很小,也可能几百兆。一次性 f.read() 会全部加载进内存,对大文件不友好。我一般按行读取:
with open('large_report.log', 'r') as f:
for line in f:
process(line) # 逐行处理
为什么用 for line in f 而不是 f.readlines()?readlines() 会把所有行读到列表中,依然占内存。而迭代器方式每次只读一行,内存使用恒定的。几年前我在解析一个 2GB 的 docking 输出时,改用迭代器后内存从 800MB 降到了 12MB。
如果需要读取指定行数,可以用 itertools.islice 或手动加计数器。
with open('report.log') as f:
for line_num, line in enumerate(f, 1):
if line_num == 10:
print(line.strip())
break
字符串常用方法——拆解报告的关键
读进来的每一行都是字符串。怎么把它变成结构化的数据?全靠这些方法:
| 方法 | 作用 | 示例 |
|---|---|---|
.strip() | 去除两端空白(含换行符) | " Score: 1.2 \n".strip() -> "Score: 1.2" |
.split() | 按分隔符拆成列表 | "A B C".split() -> ['A','B','C'] |
.startswith() | 判断前缀 | 常用于跳过注释行 |
.find() | 查找子串位置 | "molprobity".find("pro") -> 3 |
.replace() | 替换子串 | 统一分隔符或清洗特殊字符 |
实战案例: 解析 MolProbity 报告的 Ramachandran 输出行通常会这样:
line = " Ramachandran outliers: 2 residues ( 0.8% )"
# 提取数值
parts = line.split(':')[-1].strip()
number = parts.split()[0] # '2'
.strip() 一定要养成习惯。文件行尾的换行符和空格经常导致奇怪的 bug。
编码处理——避开乱码陷阱
编码问题是批量处理里最容易忽略的坑。你以为文件是 UTF-8,实际上是 Latin-1 或 GBK。我曾经有一个项目,处理了 200 个 PDB 文件都很正常,第 201 个突然崩了——原因是那个文件由一个上古工具生成,用的居然是 Mac Roman 编码。
默认编码与指定编码
open() 在 Python 3 中默认使用平台编码(Windows 下是 gbk,Linux 是 utf-8)。跨平台脚本一定要显式指定:
with open('data.txt', 'r', encoding='utf-8') as f:
...
如果遇到未知编码,可以用 chardet 库检测,或采用更稳妥的方式——用 errors='replace' 或 errors='ignore' 兜底,至少让程序不崩。
with open('report.cif', 'r', encoding='utf-8', errors='replace') as f:
...
BOM 头处理
有些 Windows 编辑器会在文本文件开头加 BOM(字节顺序标记),例如 '\ufeff'。读第一行时要手动去除:
with open('file.txt', 'r', encoding='utf-8-sig') as f:
first_line = f.readline() # encoding='utf-8-sig' 自动去掉 BOM
我一般用 utf-8-sig 代替 utf-8,这样不管有没有 BOM 都能正确处理。
errors='replace'。
核心总结:
- 用
with open(...) as f代替手动 close - 逐行迭代处理大文件,降低内存峰值
- 字符串方法
.strip()、.split()、.startswith()是解析报告的主力 - 编码问题必须显式指定,并加
errors='replace'兜底