Python 基础回顾:文件读写与数据清洗

做 GROMACS 批处理质控,说白了第一步就是把数据从磁盘上弄进来,弄干净。这一步如果写不好,后面所有分析都是空中楼阁。我个人习惯在开始任何脚本之前,先把文件读写和数据清洗模块搭好——像搭积木一样,基础稳了,上层才能跑得稳。

文件读写:打开、读、写、关

Python 里文件读写最常用的就是内置的 open() 函数,配合 with 上下文管理器。为什么推荐 with?它会在代码块结束自动关闭文件,省得你忘了关导致资源泄漏。我在项目里见过有人用 f = open() 然后忘了 f.close(),结果跑大批量的时候文件描述符耗尽……嗯,从那以后我所有代码都用 with。

常用模式:

  • 'r' — 只读(默认),文件必须存在。
  • 'w' — 写入,会覆盖已有文件。
  • 'a' — 追加,在文件末尾写入。
  • 'rb' / 'wb' — 二进制模式,处理日志或二进制输出。

举一个读取 GROMACS 能量文件的例子:

with open('ener.edr', 'rb') as f:
    # 二进制模式读取 edr 文件
    raw = f.read()
# 接下来用 xdrlib 或工具包解析

实际处理文本日志(比如 md.log)时,我更习惯逐行读:

with open('md.log', 'r', encoding='utf-8', errors='replace') as f:
    for line in f:
        # 处理每一行
        print(line.strip())
注意:编码问题是个大坑。很多生物软件输出日志默认用本地编码,如果你直接 open('r') 可能收到 UnicodeDecodeError。我建议始终指定 encoding='utf-8' 并加上 errors='replace',遇到非法字符替换掉而不是炸掉。

写入也类似,用 'w' 或 'a' 模式:

with open('result.txt', 'w') as f:
    f.write('phi_mean,psi_mean\n')
    for phi, psi in zip(phi_list, psi_list):
        f.write(f'{phi:.3f},{psi:.3f}\n')

数据清洗:把脏数据变整齐

真实项目中的日志文件经常包含多余空格、空行、注释行。比如 GROMACS 的 .xvg 文件,开头有若干以 @ 或 # 开头的元数据行。你必须把这些清理掉才能拿到纯数据。

我个人常用的几个清洗手段:

  • strip() — 去掉首尾空白符。
  • split() — 默认按任何空白切分,处理变长空格很省心。
  • startswith() — 过滤注释/标题行。
  • 正则 re.sub() — 替换多余空格或特殊符号。

举个经典的过滤 xvg 文件的代码:

import re

with open('traj.xvg', 'r') as f:
    lines = []
    for line in f:
        line = line.strip()
        if not line or line.startswith(('@', '#', '&')):
            continue
        # 将多个空格替换为单个空格
        line = re.sub(r'\s+', ' ', line)
        lines.append(line)

# 现在 lines 是纯数据行,可以用 split() 转为数字列表
data = [list(map(float, row.split())) for row in lines]
小技巧:处理 CSV 或制表符分隔的文件时,直接用 line.split('\t') 比 csv.reader 更快。但如果遇到转义、引号,还是得用 csv 模块。我在清洗出几千个 log 文件时发现 split 快了三倍,代价是丢失了 CSV 的健壮性——自己权衡。

实战对比:经典 vs 推荐写法

场景 容易犯错的写法 我推荐的写法
读取并过滤空行 for line in open('f'): 不关闭文件 with open('f') as f: for line in f: 自动关闭
处理大量文本 content = f.read().splitlines() 内存爆炸 逐行迭代 for line in f:
数字提取 用 split(' ') 遇到连续空格就挂 split() 默认按任意空白
字符串判断 if line[0] == '#': 可能越界 if line.startswith('#'): 安全

避坑指南:我踩过的几个雷

  • 曾经我写批处理脚本时,忘了在 write 后加 \n,结果所有输出粘成一坨。后来我统一用 f.write(line + '\n') 或 print(line, file=f)。
  • 曾经读取一批 Windows 下生成的 log,发现每行末尾有 \r\n,导致 split() 意外。解决办法是用 line.replace('\r', '') 或 line.rstrip()。
  • 曾经在循环里反复 open 同一个文件(比如追加模式)写入,性能极差。应该先收集所有行,最后一次性写入,或者用 with 保持打开。

核心法则:读文件用 with open + 迭代器;写文件先准备数据;清洗数据用 strip + split + startswith;遇到不确定的编码一律 encoding='utf-8', errors='replace'。

知识结构图:从原始文件到结构化数据

文件读写与数据清洗流程 原始文件 .log / .xvg / .edr 打开与读取 with open + 模式 数据清洗 strip / split startswith / re.sub 结构化数据 list of numbers 输出文件 .csv / .npy / .h5 避坑点 编码 / 换行 / 内存爆炸

你从这张图里可以看到,整个流程并不复杂。真正考验人的是细节——编码、换行、异常处理。把这些基础打牢,后续处理成百上千个模拟轨迹文件时,你就不会手忙脚乱了。