3、文件系统操作:os模块基础、pathlib库使用、批量遍历目录、文件筛选与过滤
做标定数据处理,说白了第一步就是跟文件打交道。
我刚开始写脚本那会儿,最头疼的不是算法逻辑,而是怎么把几百个文件夹里的数据文件找出来。你想想看,一个标定项目下来,原始数据、中间结果、最终标定表,散落在十几个子目录里。手动翻?那得翻到猴年马月去。
所以这一章,咱们就把文件系统操作这块彻底捋清楚。我保证,学完这些,你写批处理脚本的效率至少翻一倍。
3.1 os模块:老牌劲旅,但依然能打
Python 的 os 模块,算是文件操作的元老级人物了。我入行那会儿,几乎所有文件路径操作都靠它。虽然现在有了更现代的 pathlib,但 os 模块在一些底层操作上依然不可替代。
3.1.1 路径拼接与判断
标定数据通常按日期或项目编号分文件夹存放。比如:
D:\CalibrationData\2024_ProjectA\raw_data\sensor_01.dat
用 os 模块拼接路径,我习惯这么写:
import os
base_dir = r"D:\CalibrationData\2024_ProjectA"
sub_dir = "raw_data"
file_name = "sensor_01.dat"
full_path = os.path.join(base_dir, sub_dir, file_name)
print(full_path)
# 输出: D:\CalibrationData\2024_ProjectA\raw_data\sensor_01.dat
这里有个坑,我踩过好几次——路径分隔符。Windows 用反斜杠 \,Linux 用正斜杠 /。用 os.path.join 就对了,它会自动适配当前系统。
r,变成原始字符串,这样就不用担心反斜杠转义的问题了。比如 r"D:\data"。
3.1.2 判断文件或目录是否存在
批处理脚本最怕什么?路径不存在,程序直接崩掉。我一般会在脚本开头做一次存在性检查:
import os
data_path = r"D:\CalibrationData\2024_ProjectA"
if os.path.exists(data_path):
print("路径存在,继续处理...")
else:
print(f"警告:路径 {data_path} 不存在!")
# 可以在这里退出或创建目录
os.makedirs(data_path, exist_ok=True) # 自动创建多级目录
os.makedirs 配合 exist_ok=True,是我最常用的组合。不管目录存不存在,都能安全处理,不会报错。
3.1.3 获取文件信息
标定数据文件往往有命名规范,但有时候也需要看文件本身的属性。比如文件大小、修改时间:
import os
import time
file_path = r"D:\CalibrationData\2024_ProjectA\raw_data\sensor_01.dat"
file_size = os.path.getsize(file_path) # 字节数
mod_time = os.path.getmtime(file_path) # 时间戳
print(f"文件大小: {file_size / 1024:.2f} KB")
print(f"最后修改时间: {time.ctime(mod_time)}")
我曾经遇到过一个情况:某批次标定数据文件大小异常,只有正常文件的十分之一。用 os.path.getsize 一查,发现是采集过程中断导致的。嗯,从那以后,我每次批处理都会先检查文件大小是否在合理范围内。
3.2 pathlib库:更现代、更优雅的方式
说实话,我第一次用 pathlib 的时候,心里是有点抵触的——毕竟 os 模块用了那么多年。但用了一个月之后,我就真香了。
pathlib 把路径当作对象来处理,而不是字符串。这意味着你可以用 . 来调用各种方法,代码读起来就像在说人话。
3.2.1 基本用法
from pathlib import Path
# 定义一个路径对象
data_dir = Path(r"D:\CalibrationData\2024_ProjectA")
# 拼接路径
raw_dir = data_dir / "raw_data" / "sensor_01.dat"
print(raw_dir)
# 输出: D:\CalibrationData\2024_ProjectA\raw_data\sensor_01.dat
# 判断是否存在
if raw_dir.exists():
print("文件存在")
# 获取父目录
print(raw_dir.parent) # D:\CalibrationData\2024_ProjectA\raw_data
print(raw_dir.parents[1]) # D:\CalibrationData\2024_ProjectA
# 获取文件名和后缀
print(raw_dir.name) # sensor_01.dat
print(raw_dir.stem) # sensor_01
print(raw_dir.suffix) # .dat
看到没?用 / 运算符拼接路径,比 os.path.join 直观多了。我个人特别喜欢 .stem 和 .suffix 这两个属性,在筛选文件时简直不要太方便。
3.2.2 创建与删除目录
from pathlib import Path
# 创建目录(自动创建父目录)
output_dir = Path(r"D:\CalibrationData\2024_ProjectA\processed")
output_dir.mkdir(parents=True, exist_ok=True)
# 删除空目录
empty_dir = Path(r"D:\CalibrationData\temp")
if empty_dir.exists():
empty_dir.rmdir() # 只能删除空目录
# 删除文件
file_to_delete = Path(r"D:\CalibrationData\old_data.dat")
if file_to_delete.exists():
file_to_delete.unlink()
rmdir() 只能删除空目录。如果目录里有文件,会报错。要删除非空目录,可以用 shutil.rmtree(),但一定要谨慎——删了就找不回来了。
3.3 批量遍历目录:从手动到自动
这才是重头戏。标定工程师每天面对的就是成百上千个文件,手动处理?不存在的。
3.3.1 使用 os.walk 遍历
os.walk 是我早期最依赖的函数。它会递归遍历目录树,返回三个值:当前目录路径、子目录列表、文件列表。
import os
root_dir = r"D:\CalibrationData\2024_ProjectA"
for dirpath, dirnames, filenames in os.walk(root_dir):
print(f"当前目录: {dirpath}")
print(f"子目录数: {len(dirnames)}")
print(f"文件数: {len(filenames)}")
print("-" * 40)
# 处理每个文件
for filename in filenames:
full_path = os.path.join(dirpath, filename)
# 这里可以写你的处理逻辑
print(f" 处理文件: {full_path}")
我记得有一次,客户要求把所有子目录下的 .csv 文件汇总到一个表格里。用 os.walk 配合列表推导式,十几行代码就搞定了。客户还以为我熬了一整夜呢。
3.3.2 使用 pathlib 的 rglob 方法
如果你喜欢 pathlib 的风格,那 rglob 绝对是你的菜。它支持通配符匹配,写起来更简洁。
from pathlib import Path
data_dir = Path(r"D:\CalibrationData\2024_ProjectA")
# 查找所有 .dat 文件
dat_files = list(data_dir.rglob("*.dat"))
print(f"找到 {len(dat_files)} 个 .dat 文件")
# 查找所有子目录下的 .csv 文件
csv_files = list(data_dir.rglob("**/*.csv"))
print(f"找到 {len(csv_files)} 个 .csv 文件")
# 遍历并处理
for file_path in data_dir.rglob("*.dat"):
print(f"处理: {file_path.name}")
# 你的处理逻辑...
rglob 里的 ** 表示递归所有子目录。说白了,就是「不管藏多深,我都能给你揪出来」。
3.4 文件筛选与过滤:只拿我想要的
遍历目录只是第一步。真正麻烦的是,怎么从一堆乱七八糟的文件里,精准地找到你要处理的那一批。
3.4.1 按扩展名筛选
这是最基础的需求。标定数据常见的格式有 .dat、.csv、.h5、.mat 等。
from pathlib import Path
data_dir = Path(r"D:\CalibrationData\2024_ProjectA")
# 只筛选 .dat 和 .csv 文件
target_extensions = {".dat", ".csv"}
filtered_files = [
f for f in data_dir.rglob("*")
if f.suffix.lower() in target_extensions
]
print(f"筛选出 {len(filtered_files)} 个目标文件")
3.4.2 按文件名模式筛选
有时候文件名里包含了关键信息,比如传感器编号、采集时间。我们可以用正则表达式来精确匹配。
import re
from pathlib import Path
data_dir = Path(r"D:\CalibrationData\2024_ProjectA")
# 匹配 sensor_ 开头,后跟两位数字,扩展名为 .dat 的文件
pattern = re.compile(r"sensor_(\d{2})\.dat$")
matched_files = []
for file_path in data_dir.rglob("*.dat"):
match = pattern.search(file_path.name)
if match:
sensor_id = match.group(1)
matched_files.append((sensor_id, file_path))
print(f"传感器 {sensor_id}: {file_path.name}")
print(f"匹配到 {len(matched_files)} 个传感器文件")
我曾经遇到过一个项目,文件名里包含了采集日期,但格式不统一——有的用 20240101,有的用 2024-01-01。用正则表达式一把梭,全给标准化了。
3.4.3 按文件属性筛选
除了文件名,文件大小和修改时间也是重要的筛选条件。
from pathlib import Path
import time
data_dir = Path(r"D:\CalibrationData\2024_ProjectA")
# 筛选最近7天内修改过的 .dat 文件
seven_days_ago = time.time() - 7 * 24 * 3600
recent_files = [
f for f in data_dir.rglob("*.dat")
if f.stat().st_mtime > seven_days_ago
]
print(f"最近7天修改的文件数: {len(recent_files)}")
# 筛选大小在 1KB 到 10MB 之间的文件
min_size = 1024 # 1KB
max_size = 10 * 1024 * 1024 # 10MB
valid_files = [
f for f in data_dir.rglob("*.dat")
if min_size <= f.stat().st_size <= max_size
]
print(f"大小在范围内的文件数: {len(valid_files)}")
3.5 实战:一个完整的标定数据筛选脚本
说了这么多,咱们来写一个实际能用的脚本。假设场景是这样的:
- 数据目录结构:
项目名/日期/传感器ID/*.dat - 需要筛选出:2024年1月之后、传感器ID为01-10、文件大小大于100KB的
.dat文件
from pathlib import Path
import re
def filter_calibration_data(root_dir):
"""
筛选标定数据文件
"""
root = Path(root_dir)
if not root.exists():
print(f"错误:目录 {root_dir} 不存在")
return []
# 编译正则:匹配 sensor_01.dat 到 sensor_10.dat
pattern = re.compile(r"sensor_(0[1-9]|10)\.dat$")
# 日期过滤:2024年1月1日之后
from datetime import datetime
cutoff_date = datetime(2024, 1, 1).timestamp()
result = []
for file_path in root.rglob("*.dat"):
# 条件1:文件名匹配
if not pattern.search(file_path.name):
continue
# 条件2:文件大小大于100KB
if file_path.stat().st_size < 100 * 1024:
continue
# 条件3:修改时间在2024年1月之后
if file_path.stat().st_mtime < cutoff_date:
continue
result.append(file_path)
return result
# 使用示例
if __name__ == "__main__":
data_dir = r"D:\CalibrationData"
files = filter_calibration_data(data_dir)
print(f"筛选到 {len(files)} 个符合条件的文件")
for f in files:
print(f" - {f}")
这个脚本我实际用过类似的版本。当时项目组有5个人,每个人手动筛选数据,每次要花半小时。写成脚本后,5秒钟搞定。你想想看,这效率提升有多大?
3.6 避坑指南
- 路径编码问题: 中文路径在 Windows 下可能乱码。建议项目路径全用英文,或者统一用 UTF-8 编码。
- 权限问题: 有些系统目录(如 Program Files)普通用户没有写权限。脚本里最好加个 try-except 捕获 PermissionError。
- 符号链接:
os.walk默认会进入符号链接指向的目录,可能导致无限循环。用followlinks=False可以避免。 - 文件占用: 有些文件正在被其他程序使用,删除或移动时会报错。操作前先检查文件是否可访问。
好了,文件系统操作这块就讲到这里。下一章咱们要进入真正的数据处理环节——用 Python 读写各种标定数据格式。到时候你会发现,文件操作学好了,后面的路会顺很多。