3. 文件 I/O 操作:读取配置文件与写入日志

文件操作,说白了就是让脚本跟硬盘上的数据打交道。在配方自动切换这个场景里,我们至少需要搞定三件事:读配置、写日志、管好路径。这三件事要是没做好,脚本跑起来就跟没头苍蝇一样。

我个人习惯把文件操作分成两类:一类是脚本启动时一次性读取的静态数据,比如配方参数、设备地址;另一类是运行时持续写入的动态数据,比如切换记录、报错信息。前者用配置文件,后者用日志文件。

3.1 读取配置文件:INI / JSON / CSV

配置文件就像脚本的「记忆卡」。你不想每次改个参数都去翻代码吧?把可变参数抽到配置文件里,改配置就行,代码不用动。

3.1.1 INI 格式:老派但够用

INI 文件结构简单,就是节(Section)+ 键值对。适合存设备参数、通信端口这类固定结构的数据。

; config.ini
[Device]
IP = 192.168.1.100
Port = 502
Timeout = 5

[Recipe]
Default = recipe_001.csv
RetryCount = 3

Python 里用 configparser 模块读取,几行代码搞定:

import configparser

config = configparser.ConfigParser()
config.read('config.ini')

ip = config['Device']['IP']
port = config['Device'].getint('Port')
timeout = config['Device'].getfloat('Timeout')

print(f"设备地址:{ip}:{port},超时:{timeout}s")
我的习惯:用 getint()、getfloat() 代替直接索引,避免类型转换出错。我在项目里见过有人直接用 config['Device']['Port'] 拿到的字符串去拼 SQL,结果端口号变成了字符串拼接,排查了半天。

3.1.2 JSON 格式:现代工业标准

JSON 支持嵌套结构,比 INI 灵活得多。配方数据往往有多层结构——一个配方包含多个工步,每个工步又有多个参数。这种场景 JSON 是首选。

{
  "recipe_name": "recipe_001",
  "version": "2.1",
  "steps": [
    {
      "step_id": 1,
      "action": "heat",
      "target_temp": 180,
      "hold_time": 30
    },
    {
      "step_id": 2,
      "action": "cool",
      "target_temp": 25,
      "hold_time": 15
    }
  ]
}

读取 JSON 用内置的 json 模块:

import json

with open('recipe.json', 'r', encoding='utf-8') as f:
    recipe = json.load(f)

print(f"配方名称:{recipe['recipe_name']}")
for step in recipe['steps']:
    print(f"  工步 {step['step_id']}:{step['action']} 到 {step['target_temp']}℃")
注意编码:JSON 文件默认用 UTF-8 编码。如果你在 Windows 下用记事本编辑,保存时选 UTF-8,否则中文会乱码。我曾经因为编码问题,读出来的配方名称全是乱码,导致脚本匹配不到正确的配方文件。

3.1.3 CSV 格式:表格数据的好搭档

CSV 适合存批量数据,比如配方列表、历史记录。每行一条记录,用逗号分隔字段。

recipe_id,recipe_name,target_temp,hold_time
001,recipe_heat_180,180,30
002,recipe_cool_25,25,15
003,recipe_anneal_120,120,60

Python 用 csv 模块读取:

import csv

recipes = []
with open('recipe_list.csv', 'r', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    for row in reader:
        recipes.append(row)

for r in recipes:
    print(f"{r['recipe_id']} - {r['recipe_name']}")
三种格式怎么选?
格式适用场景优点缺点
INI设备参数、固定配置简单直观,人类可读不支持嵌套
JSON配方数据、复杂结构支持嵌套,解析快手写容易漏括号
CSV批量数据、历史记录Excel 可直接打开不支持层级结构

3.2 写入日志文件

日志是脚本的「黑匣子」。配方切换出问题了,第一件事就是翻日志。没有日志,你只能靠猜。

Python 内置的 logging 模块足够用,别自己写文件追加逻辑。我见过有人用 open('log.txt', 'a') 手动写日志,结果多线程环境下日志全乱了。

import logging

logging.basicConfig(
    filename='recipe_switch.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

logging.info('配方切换开始:recipe_001')
logging.warning('温度偏差超过阈值:+3℃')
logging.error('配方文件未找到:recipe_999.json')

日志级别从低到高:DEBUG、INFO、WARNING、ERROR、CRITICAL。生产环境一般设 INFO 级别,DEBUG 只在调试时开。

日志轮转:日志文件不能无限增长。用 RotatingFileHandler 按大小切分,或者 TimedRotatingFileHandler 按时间切分。我习惯每天一个日志文件,保留 30 天。
from logging.handlers import TimedRotatingFileHandler

handler = TimedRotatingFileHandler(
    'recipe_switch.log',
    when='midnight',
    interval=1,
    backupCount=30
)
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger = logging.getLogger()
logger.addHandler(handler)

3.3 文件路径处理

路径处理是文件操作里最容易翻车的地方。Windows 用反斜杠 \,Linux 用正斜杠 /。硬编码路径的脚本,换个环境就崩。

用 os.path 或 pathlib 处理路径,跨平台无压力。我个人更推荐 pathlib,API 更现代。

from pathlib import Path

# 当前脚本所在目录
base_dir = Path(__file__).parent

# 配置文件路径
config_path = base_dir / 'config' / 'device.ini'

# 日志文件路径
log_path = base_dir / 'logs' / 'recipe_switch.log'

# 确保目录存在
log_path.parent.mkdir(parents=True, exist_ok=True)

print(f"配置文件:{config_path}")
print(f"日志文件:{log_path}")
路径拼接陷阱:不要用字符串拼接路径!base_dir + '/' + 'config' + '/' + 'device.ini' 这种写法在 Windows 下会出问题。用 os.path.join() 或 pathlib 的 / 运算符。

3.4 知识结构图:文件 I/O 操作全景

文件 I/O 操作知识结构 文件 I/O 操作 读取配置文件 写入日志文件 文件路径处理 INI 格式 JSON 格式 CSV 格式 日志级别控制 日志轮转策略 os.path / pathlib 核心原则 配置参数不硬编码 · 日志记录不可少 · 路径处理跨平台 用标准库 · 别自己造轮子 · 编码统一用 UTF-8 稳定可靠的配方切换基础

3.5 实战建议:把这些串起来

在实际项目中,我通常这样组织文件操作:

  1. 启动时:用 JSON 读取配方数据,用 INI 读取设备参数
  2. 运行时:每次切换配方,记录一条 INFO 日志
  3. 异常时:捕获异常,写入 ERROR 日志,并记录当前上下文
  4. 路径管理:所有路径用 pathlib 动态生成,不写死
避坑清单:
  • 文件路径不要用相对路径,用 __file__ 定位脚本所在目录
  • 日志文件要轮转,别让一个文件撑爆硬盘
  • 配置文件编码统一用 UTF-8,避免跨平台乱码
  • 读取文件时加 try-except,文件不存在要有默认值或报错提示

嗯,文件 I/O 这块其实不难,但细节多。你想想看,一个配方切换脚本跑在生产线上,突然因为路径写死找不到配置文件,或者日志文件撑爆了硬盘——这种低级错误,咱们不能犯。