资讯详情

资讯详情

用Python打造机车运行测试记录管理工具

做机车运行数据整理时最容易出问题的地方往往不是计算而是记录本身。比如新配属的一台Class 20型机车编号D8069在新家完成首次加载测试运行同一天还有一台Class 24型机车34092按照货物编组方式继续做运行记录。这些信息如果不落到结构化表格里后面做月度统计、车型对比、试验次数汇总时就会变得很被动。本文就用一个真实的记录管理需求来展开写一套基于 Python 的机车运行测试记录管理工具把日期、车型、机车编号、测试类型、编组方式、备注这些信息统一管起来同时支持查询、统计和 CSV 持久化。无论你是刚接触 Python 的数据初学者还是需要在运维场景维护设备台账的开发者都可以把这套代码当作一个能直接改造使用的小工具。1. 背景与核心概念1.1 什么是机车运行测试记录机车运行测试记录简单说就是把一台机车在某个时间点完成的测试任务记录下来。这里的“测试”不一定是实验室里那种严格的性能试验也包括上线运行过程中的加载测试、空载试验、货物编组运行等。比如文章中提到的“FIRST loaded test run for class 20, D8069 at her new home”翻译成业务语言就是一台编号为 D8069 的 Class 20 型机车在它的新配属地点完成了首次加载测试运行。这类记录最大的价值在于可追溯。一台机车从出厂到投入正常运营中间会经历多次测试。如果没有记录后去排查机车状态、确认历史运行条件时只能依赖人的记忆。而一旦涉及多台机车、多类测试、不同编组方式数据量会迅速增长这时就需要用程序来管理。从技术角度看每条测试记录本质上是一组字段的组合主要包括日期测试完成的时间。车型机车的型号类别如 Class 20、Class 24。机车编号用于唯一定位一台机车如 D8069、34092。测试类型如首次加载测试、货物编组运行测试。编组方式描述列车采用哪种编组形式常见的有客车编组、货物编组等。备注记录其他需要补充说明的信息。把这些字段用统一格式保存下来就形成了一份最基础的“运行测试记录数据集”。1.2 为什么需要代码来管理记录很多人会问记录放 Excel 里不就行了吗对于几十条数据确实可以但一旦数据量上来Excel 有几个明显痛点第一格式不统一。不同人填写同一列时可能写出“加载测试”“loaded test”“首次加载”等多种说法后期统计时很难聚合。第二查询效率低。想查一台机车做过哪些测试通常需要筛选、排序、再人工比对操作成本高。第三缺少自动校验。Excel 不会主动提醒你日期格式是否合法、车型名称是否拼写正确。使用 Python 管理记录可以把“数据写入”“数据查询”“数据统计”这些操作全部固化下来用统一的函数接口完成。这样团队成员不需要会 Excel 高级筛选只需要按格式调用命令或函数即可。1.3 本文要解决的核心问题围绕前面提到的两辆机车本文要实现一个完整的记录管理系统具体包括定义一个结构化的记录存储格式使用 CSV 文件保存数据。实现添加记录、读取记录、按车型或编号查询、按测试类型统计等功能。用真实的示例数据验证整套流程示例数据包含 Class 20 型 D8069 和 Class 24 型 34092。提供常见问题排查思路帮助你在自己的数据上快速复用。这套工具的特点是不依赖第三方库仅使用 Python 标准库中的 csv、os、datetime因此开箱即用对运行环境要求很低。2. 环境准备与版本说明2.1 运行环境本文代码在以下环境中验证通过操作系统Windows 10 / 11 或 Linux 均可Python 版本Python 3.8 及以上建议使用 Python 3.10 或更高版本代码编辑器VS Code、PyCharm 或任意文本编辑器Python 3.8 以下版本也能运行核心逻辑但类型标注中的部分写法可能会有兼容性差异。如果使用的是老版本 Python可以先把类型标注去掉不影响功能。2.2 依赖库说明为了降低使用门槛本文实现不依赖 pandas、numpy 等第三方库只使用 Python 标准库模块用途csv读写 CSV 文件实现数据持久化os处理文件路径自动创建目录datetime校验和格式化日期typing类型标注方便代码维护这样做的优势很明显在任何一台安装了 Python 的电脑上不需要执行 pip install 就能直接运行。2.3 项目结构建议按照下面的目录结构组织代码train_record/ │ ├── data/ │ └── test_records.csv # 存放测试记录数据 │ ├── src/ │ └── train_log_manager.py # 核心管理模块 │ └── main.py # 程序入口演示功能如果目录还不存在可以直接在命令行中创建mkdir train_record cd train_record mkdir data src3. 数据模型与核心设计3.1 记录字段设计在开始写代码之前先对每条记录的数据模型做一个清晰约定。字段名统一使用小写和下划线命名方便在真实项目中保持不变。字段名含义示例值date测试日期2025-04-02test_type测试类型first_loaded_testtrain_class车型Class 20number机车编号D8069marshalling编组方式loadednote备注信息新配属段首次加载测试这里重点说明几个字段test_type 使用英文下划线风格避免中文文本在 CSV 编码中出现兼容问题同时也便于后续做聚合统计。train_class 与 number 是定位一台机车的联合标识。现实中车型和编号可能重复比如不同路局都可能存在“Class 20”因此查询时建议同时带上车型和编号。marshalling 描述编组情况。文中的“CoW”可以理解为一种货物编组形式但不同路局对这类缩写定义不完全一致。实操中建议在备注中写清楚完整含义避免歧义。3.2 如何描述“首次加载测试运行”“FIRST loaded test run for class 20, D8069 at her new home”翻译成结构化数据可以拆成两部分测试类型为 first_loaded_test表示首次加载测试。备注中写明“at her new home”对应的信息即“新配属段”。在 CSV 数据中这一条记录写成date,test_type,train_class,number,marshalling,note 2025-04-02,first_loaded_test,Class 20,D8069,loaded,新配属段首次加载测试同样的思路34092 这台 Class 24 型机车执行货物编组运行测试可以写成2025-04-02,goods_marshalling_test,Class 24,34092,cow,货物编组方式运行测试这里把 CoW 作为编组方式缩写记录。如果你的业务中有统一的缩写表建议维护一份“缩写-完整含义”对照字典否则后期看到数据时很容易误解。3.3 CSV 的编码选择CSV 文件最常见的两个坑是编码和分隔符。Python 内置 csv 模块写入文件时可以选择 encoding 参数。为了兼容 Excel 直接打开推荐使用 utf-8-sig 编码它会在文件头部写入 BOMExcel 打开时不会出现中文乱码。分隔符默认是英文逗号如果某个字段中包含了逗号csv 模块会自动加上引号包裹读取时也能正确还原。因此不需要手动转义。4. 完整实战案例4.1 初始化数据文件首先在 data 目录下创建一个空的 CSV 文件或者由程序自动创建。我们选择由程序自动初始化减少手工操作。在项目根目录下执行python -c from src.train_log_manager import TrainLogManager; TrainLogManager().initialize()这样会在 data 目录下生成 test_records.csv并写入表头。如果你希望手动创建也可以直接新建文件内容如下date,test_type,train_class,number,marshalling,note4.2 编写核心管理模块下面创建核心模块src/train_log_manager.py这是整个工具的骨架。# 文件路径src/train_log_manager.py 机车运行测试记录管理工具 支持新增记录、查询记录、统计记录。 import csv import os from datetime import datetime from typing import List, Dict, Optional BASE_DIR os.path.dirname(os.path.abspath(__file__)) DEFAULT_CSV_PATH os.path.join(BASE_DIR, .., data, test_records.csv) HEADERS [ date, test_type, train_class, number, marshalling, note, ] class TrainLogManager: 机车测试记录管理器 def __init__(self, csv_path: str DEFAULT_CSV_PATH): self.csv_path os.path.abspath(csv_path) self._ensure_file() def _ensure_file(self): 如果数据文件不存在则自动创建并写入表头 data_dir os.path.dirname(self.csv_path) if not os.path.exists(data_dir): os.makedirs(data_dir) if not os.path.exists(self.csv_path): self.initialize() def initialize(self): 创建一个空的数据文件只包含表头 with open(self.csv_path, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesHEADERS) writer.writeheader() def add_record( self, date: str, test_type: str, train_class: str, number: str, marshalling: str , note: str , ): 新增一条测试记录。 参数 date 要求格式为 YYYY-MM-DD 内部会做格式校验避免写入不合法日期。 # 校验日期格式 try: datetime.strptime(date, %Y-%m-%d) except ValueError: raise ValueError(日期格式错误必须为 YYYY-MM-DD例如 2025-04-02) # 校验必填字段 if not test_type or not train_class or not number: raise ValueError(test_type、train_class、number 都不能为空) record { date: date, test_type: test_type, train_class: train_class, number: number, marshalling: marshalling, note: note, } with open(self.csv_path, a, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesHEADERS) writer.writerow(record) def read_all(self) - List[Dict[str, str]]: 读取全部记录按原始顺序返回 records [] with open(self.csv_path, r, encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: records.append(row) return records def find_by_class(self, train_class: str) - List[Dict[str, str]]: 按车型查询例如 Class 20 return [ r for r in self.read_all() if r.get(train_class, ).strip().lower() train_class.strip().lower() ] def find_by_number(self, number: str) - List[Dict[str, str]]: 按机车编号查询例如 D8069 return [ r for r in self.read_all() if r.get(number, ).strip().lower() number.strip().lower() ] def find_by_test_type(self, test_type: str) - List[Dict[str, str]]: 按测试类型查询 return [ r for r in self.read_all() if r.get(test_type, ).strip().lower() test_type.strip().lower() ] def stats(self) - Dict[str, int]: 统计功能 返回总记录数、按车型分布、按测试类型分布。 records self.read_all() total len(records) class_stat {} type_stat {} for r in records: cls r.get(train_class, unknown) tp r.get(test_type, unknown) class_stat[cls] class_stat.get(cls, 0) 1 type_stat[tp] type_stat.get(tp, 0) 1 return { total: total, by_class: class_stat, by_test_type: type_stat, } def show_all(self): 以表格形式打印全部记录方便控制台查看 records self.read_all() if not records: print(当前没有任何记录。) return print(f{日期:12} {测试类型:22} {车型:10} {编号:10} {编组:10} 备注) print(- * 80) for r in records: print( f{r.get(date, ):12} f{r.get(test_type, ):22} f{r.get(train_class, ):10} f{r.get(number, ):10} f{r.get(marshalling, ):10} f{r.get(note, )} )这段代码覆盖了核心功能下面逐个解释。_ensure_file方法会在初始化时自动创建目录和数据文件避免用户手动建目录。add_record方法先校验日期再校验必填字段最后追加写入 CSV。注意写入时使用newline这是 Python 写 CSV 的固定写法防止在 Windows 下出现空行。read_all方法用DictReader读取每一行记录的字段返回列表。列表中的每个元素是一个字典。find_by_class、find_by_number、find_by_test_type三个查询方法都做了大小写统一处理查询时不区分大小写更符合实际使用习惯。stats方法返回一个字典包含总记录数、车型分布、测试类型分布。show_all方法主要是为了方便在控制台直接查看数据。4.3 编写程序入口接下来创建入口文件main.py演示如何添加两条示例记录并执行查询统计。# 文件路径main.py from src.train_log_manager import TrainLogManager def main(): # 初始化管理器如果文件不存在会自动创建 manager TrainLogManager() # —— 添加示例记录 —— # 记录1Class 20 型 D8069 在新配属段首次加载测试运行 manager.add_record( date2025-04-02, test_typefirst_loaded_test, train_classClass 20, numberD8069, marshallingloaded, note新配属段首次加载测试运行, ) # 记录2Class 24 型 34092 货物编组测试运行 manager.add_record( date2025-04-02, test_typegoods_marshalling_test, train_classClass 24, number34092, marshallingcow, note货物编组方式运行测试, ) # —— 输出全部记录 —— print( 全部测试记录 ) manager.show_all() # —— 按车型查询 —— print(\n 查询 Class 20 型机车记录 ) class20_records manager.find_by_class(Class 20) for r in class20_records: print(r) # —— 按编号查询 —— print(\n 查询编号 D8069 记录 ) by_number manager.find_by_number(D8069) for r in by_number: print(r) # —— 统计信息 —— print(\n 统计信息 ) stats manager.stats() print(f总记录数{stats[total]}) print(f按车型分布{stats[by_class]}) print(f按测试类型分布{stats[by_test_type]}) if __name__ __main__: main()这段入口代码可以直观地看到整个工具的使用流程。值得说明的是manager.add_record()每次调用都会打开一次 CSV 文件写入一行。如果只有几十条记录性能没有问题如果一次性需要批量导入上万条记录建议单独写一个批量导入方法减少文件 IO 次数。4.4 运行与验证在项目根目录执行python main.py预期输出类似 全部测试记录 日期 测试类型 车型 编号 编组 备注 -------------------------------------------------------------------------------- 2025-04-02 first_loaded_test Class 20 D8069 loaded 新配属段首次加载测试运行 2025-04-02 goods_marshalling_test Class 24 34092 cow 货物编组方式运行测试 查询 Class 20 型机车记录 {date: 2025-04-02, test_type: first_loaded_test, train_class: Class 20, number: D8069, marshalling: loaded, note: 新配属段首次加载测试运行} 查询编号 D8069 记录 {date: 2025-04-02, test_type: first_loaded_test, train_class: Class 20, number: D8069, marshalling: loaded, note: 新配属段首次加载测试运行} 统计信息 总记录数2 按车型分布{Class 20: 1, Class 24: 1} 按测试类型分布{first_loaded_test: 1, goods_marshalling_test: 1}如果你的输出与上面一致说明整条链路已经跑通新增记录写入 CSV查询能正确检索到对应数据统计结果也能准确汇总。4.5 批量导入示例数据如果从旧系统迁移数据通常是一次性拿到大量历史记录。这时可以写一个批量导入的辅助方法。# 文件路径src/train_log_manager.py 中新增方法 def add_many(self, records: List[Dict[str, str]]): 批量添加记录。 参数 records 是一个字典列表每个字典必须包含当前 data 表头中的所有字段。 这个方法会提前做日期校验如果某条记录不合法直接抛出异常。 valid_records [] for rec in records: date_val rec.get(date, ) try: datetime.strptime(date_val, %Y-%m-%d) except ValueError: raise ValueError(f日期格式错误{date_val}必须为 YYYY-MM-DD) if not rec.get(test_type) or not rec.get(train_class) or not rec.get(number): raise ValueError(test_type、train_class、number 都不能为空) valid_records.append({ date: rec.get(date, ), test_type: rec.get(test_type, ), train_class: rec.get(train_class, ), number: rec.get(number, ), marshalling: rec.get(marshalling, ), note: rec.get(note, ), }) with open(self.csv_path, a, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesHEADERS) writer.writerows(valid_records)调用方式records [ { date: 2025-04-02, test_type: first_loaded_test, train_class: Class 20, number: D8069, marshalling: loaded, note: 新配属段首次加载测试运行, }, { date: 2025-04-02, test_type: goods_marshalling_test, train_class: Class 24, number: 34092, marshalling: cow, note: 货物编组方式运行测试, }, ] manager.add_many(records)批量导入的优势在于只打开一次文件写入性能明显优于逐条调用add_record。5. 常见问题与排查思路在编写和运行这套工具时最容易遇到以下几类问题。下面整理成表格方便快速定位问题现象常见原因解决思路CSV 文件用 Excel 打开中文乱码编码使用了 utf-8而不是 utf-8-sig写入文件时将 encoding 参数改为 utf-8-sig写入 CSV 后多出空行打开文件时没有使用 newline写 CSV 时 open 函数必须加 newline新增记录时报日期格式错误日期不是 YYYY-MM-DD 格式统一使用 datetime.strptime 校验日期格式查询结果为空查询条件与存储值大小写不一致查询时统一 strip().lower() 后再比较找不到 data 目录没有手动创建 data 目录使用 _ensure_file 方法自动创建目录记录内容中包含英文逗号导致 CSV 列错位使用 csv 模块自动处理引号不要手动拼接字符串main.py 无法导入 TrainLogManager模块搜索路径不对在项目根目录执行而不是在 src 目录执行下面挑两个高频问题详细展开。5.1 CSV 中文乱码很多初学者在 Windows 上写 CSV 时习惯用with open(test.csv, w, encodingutf-8) as f:这样生成的文件用记事本打开没问题但用 Excel 打开常常会显示乱码。原因是 Excel 默认按 GBK 或 ANSI 编码解析没有 BOM 的文本文件。解决办法很简单把编码改成 utf-8-sigwith open(test.csv, w, newline, encodingutf-8-sig) as f:这样会在文件头写入 BOM 标记Excel 就能正确识别编码。5.2 日期校验失败如果手动构造了一条脏数据比如 date 字段写成了“2025/04/02”或“2025年4月2日”datetime.strptime会直接抛出异常。这是故意设计的宁可让程序在写入前中断也不要让错误日期进入数据文件。如果你确实需要兼容多种日期格式可以扩展校验逻辑先尝试多种strptime格式再统一转成标准格式存储def normalize_date(date_str: str) - str: for fmt in (%Y-%m-%d, %Y/%m/%d, %Y.%m.%d): try: dt datetime.strptime(date_str, fmt) return dt.strftime(%Y-%m-%d) except ValueError: continue raise ValueError(f无法识别的日期格式{date_str})推荐在正式项目中加入这个函数提高容错性。6. 最佳实践与工程建议6.1 字段命名与数据规范字段命名要统一。比如“车型”既可能被写成 train_class也可能被写成 class_type。一旦命名混乱后续的查询和统计代码就要写大量兼容逻辑。最好在项目初始化时定义一个统一的字段常量表例如# 文件路径src/constants.py HEADERS [ date, test_type, train_class, number, marshalling, note, ] TEST_TYPE_MAP { loaded: 加载测试, first_loaded_test: 首次加载测试, goods_marshalling_test: 货物编组测试, empty_running_test: 空载运行测试, }这样在写统计报表时可以直接根据类型映射输出中文描述避免同一含义多种写法的问题。6.2 数据备份与追加原则这个工具只做“追加写入”不提供修改和删除功能这是刻意选择的。测试记录本身是历史事实不建议随意修改。如果某条记录录入错误更适合新增一条“更正记录”并在备注中说明原因。这样数据链完整审计追溯时会很有价值。如果确实需要修改建议不要直接操作 CSV 文件而是在程序里先读取全部数据修改对应行再整体写回。写回前务必备份原文件。# 简单备份思路 import shutil backup_path self.csv_path .backup shutil.copyfile(self.csv_path, backup_path)6.3 生产环境的存储升级当记录数量达到几万条甚至更多时CSV 文件会暴露出两个问题查询速度变慢并发写入场景下容易出现数据覆盖。更合适的方案是升级到 SQLite。SQLite 仍然不需要安装额外的数据库服务Python 标准库自带 sqlite3 模块。可以创建一张结构相同的数据表把查询交给 SQL 完成性能和事务安全性都比 CSV 更好。后续升级步骤建议创建 SQLite 数据库和表。把现有 CSV 数据导入 SQLite。将查询接口替换为 SQL 语句。保留 CSV 导出功能方便和其他系统对接。6.4 日志与监控在实际运维场景中每次新增记录最好保留操作日志。可以在add_record方法中增加一行输出import logging logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) # 在 add_record 成功写入后 logging.info(f新增测试记录{train_class} {number} {test_type})这样当记录出现问题时可以回溯是哪一步操作写入的。7. 总结与下一步这篇实战文章围绕两条具体的机车运行测试记录展开把“FIRST loaded test run for class 20, D8069”和“34092 CoW class24”转化为结构化数据并通过 Python 标准库实现了一套完整的记录管理工具。你掌握了 CSV 的读写规范、日期校验方法、查询和统计逻辑以及常见问题的排查思路。当你准备把这套代码用在更大的项目中时优先考虑三件事第一统一数据字段和编码格式避免历史数据迁移时出现各种兼容问题第二增加数据备份机制任何修改类操作都先备份原文件第三在数据量增大后及时迁移到 SQLite保持查询性能和事务安全。接下来可以继续尝试的方向包括用 matplotlib 把车型分布、测试类型趋势画成图表把 CSV 数据导入 pandas 做更复杂的分析把工具包装成命令行程序或 Flask 接口方便团队其他成员通过网络访问。代码结构已经留好了扩展接口直接在这个基础上迭代会比重新开发省力很多。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →