文件读取与文本处理基础

做 MolProbity 报告批量解析,说白了就是跟一堆文本文件打交道。你拿到的每条 PDB、每个 log,本质上都是字符串。怎么读、怎么拆、怎么避免乱码——这几步基本功不扎实,后面所有自动化都会翻车。我个人习惯在项目开始时先花 10 分钟把「读文件」这一层封装好,后面能省掉大量调试时间。

open() 函数与文件模式

Python 内置的 open() 是打开文件的唯一入口。它的第一个参数是路径,第二个参数是模式字符串。我见过不少新手上来就写 open('file.txt'),默认是只读文本模式,没问题。但一旦要写二进制或追加内容,就懵了。

模式含义常用场景
'r'只读(文本)读取报告文件、配置文件
'w'写入(覆盖)写解析结果、生成新报告
'a'追加日志聚合、记录错误
'rb'只读二进制读取 PDB 中的非 ASCII 数据
'r+'读写原地修改文件(不常用)
避坑提醒: 我曾经在处理一批 CIF 结构文件时,以为 '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 是零成本的习惯。

我的小技巧: 同时读多个文件时用嵌套 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 都能正确处理。

曾经的血泪教训: 有一次我批量解析 MolProbity 报告,在 Linux 服务器上跑得好好的,丢到 Windows 上就报 UnicodeDecodeError。查了半天,发现是踩到 Windows 默认编码的坑。从此所有文件操作一律显式指定 encoding,并加上 errors='replace'。
文件读取与文本处理流程 1. open() 选择模式 2. with 自动管理 3. for line in f 4. strip/split/find 5. 编码 + errors 保险 注意先确认原始编码 用 utf-8-sig 防 BOM 大文件用迭代器 不用 readlines()

核心总结:

  • 用 with open(...) as f 代替手动 close
  • 逐行迭代处理大文件,降低内存峰值
  • 字符串方法 .strip()、.split()、.startswith() 是解析报告的主力
  • 编码问题必须显式指定,并加 errors='replace' 兜底