CircuitPython生态中的adafruit-circuitpython-pathlib包是一个专门为嵌入式设备设计的轻量级路径操作库。它移植了Python标准库pathlib的核心功能,让开发者能在资源受限的微控制器上以面向对象的方式处理文件系统路径。
我在多个MicroPython/CircuitPython硬件项目中使用过这个库,发现它特别适合需要频繁操作SD卡、Flash存储等外设的场景。相比直接拼接字符串路径,用pathlib可以让代码更健壮且跨平台兼容——毕竟不同嵌入式系统的路径分隔符可能不同(比如Unix风格用"/"而某些嵌入式系统用"")。
这个库的核心是Path类,它提供了以下关键方法:
python复制from pathlib import Path
# 创建Path对象(不会实际创建文件)
p = Path("/sd/logs/sensor_data.txt")
# 获取父目录
parent = p.parent # Path("/sd/logs")
# 拼接路径(自动处理分隔符)
new_path = parent / "backup" / "data.bak"
# 检查路径属性
print(p.exists()) # 实际文件是否存在
print(p.is_file()) # 是否为文件
print(p.is_dir()) # 是否为目录
注意:CircuitPython的pathlib实现是标准库的子集,不支持
symlink_to()等高级功能
针对嵌入式场景特别优化了以下操作:
python复制# 创建目录(支持递归创建)
Path("/sd/new_folder/subdir").mkdir(parents=True, exist_ok=True)
# 遍历目录(节省内存的生成器方式)
for f in Path("/sd").iterdir():
if f.suffix == ".csv":
print(f"找到CSV文件: {f}")
# 文件操作
target = Path("/sd/data.log")
target.write_text("Hello CircuitPython") # 写入文本
content = target.read_text() # 读取文本
target.unlink() # 删除文件
python复制p = Path("/sd/images/photo.jpg")
print(p.name) # "photo.jpg"
print(p.stem) # "photo"
print(p.suffix) # ".jpg"
print(p.parts) # ('/', 'sd', 'images', 'photo.jpg')
在环境监测项目中,我用Pathlib实现了自动按日期存储传感器数据:
python复制import time
from pathlib import Path
def save_sensor_data(temp, humidity):
# 创建日期格式的目录结构
date_dir = time.localtime()[:3] # (year, month, day)
log_dir = Path("/sd") / f"{date_dir[0]}" / f"{date_dir[1]:02d}"
log_dir.mkdir(parents=True, exist_ok=True)
# 创建CSV文件(如果不存在则添加表头)
log_file = log_dir / f"{date_dir[2]:02d}.csv"
if not log_file.exists():
log_file.write_text("timestamp,temp,humidity\n")
# 追加数据
with log_file.open("a") as f:
f.write(f"{time.time()},{temp:.1f},{humidity:.1f}\n")
技巧:使用
open("a")模式可以避免每次重写整个文件,这对Flash存储更友好
为产品设计无线更新功能时,Pathlib帮助可靠地管理固件包:
python复制def update_firmware(update_zip):
update_dir = Path("/sd/updates")
update_dir.mkdir(exist_ok=True)
# 验证文件完整性
if update_zip.stat().st_size < 1024:
raise ValueError("文件过小,可能下载失败")
# 创建备份
backup_path = update_dir / f"backup_{int(time.time())}.bin"
current_firmware = Path("/internal/main.bin")
backup_path.write_bytes(current_firmware.read_bytes())
# 执行更新(伪代码)
apply_update(update_zip)
嵌入式设备的存储通常速度较慢,应尽量减少实际IO操作:
python复制# 不推荐:多次检查exists()
if p.exists():
if p.is_file():
content = p.read_text()
# 推荐:单次操作
try:
content = p.read_text()
except OSError:
print("文件不存在或读取失败")
对于频繁访问的路径,可以提前创建Path对象:
python复制# 初始化时创建
LOG_ROOT = Path("/sd/logs")
# 后续直接使用
def get_log_path(name):
return LOG_ROOT / f"{name}.log"
处理大量文件时使用生成器避免内存溢出:
python复制# 查找所有CSV文件
csv_files = (p for p in Path("/sd").rglob("*.csv") if p.stat().st_size > 0)
for csv in csv_files:
process_csv(csv) # 逐个处理而不加载所有文件信息
python复制try:
p = Path("/sd/missing_dir/file.txt")
p.write_text("test")
except OSError as e:
if e.args[0] == 2: # ENOENT
print("错误:父目录不存在,请先创建目录")
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("retry") # 重试
写入前检查可用空间:
python复制import os
def safe_write(path, data):
stat = os.statvfs(path.parent)
free_kb = stat.f_bsize * stat.f_bavail / 1024
if len(data) / 1024 > free_kb * 0.9: # 保留10%余量
raise RuntimeError("存储空间不足")
path.write_text(data)
处理非ASCII文件名时:
python复制# 正确方式:直接使用字节模式
p = Path("/sd/中文测试.txt")
try:
p.write_text("content")
except UnicodeError:
print("当前文件系统不支持Unicode文件名")
python复制class DataFileFinder:
def __init__(self, root):
self.root = Path(root)
def find_recent(self, pattern="*.csv", days=7):
cutoff = time.time() - days * 86400
for f in self.root.rglob(pattern):
if f.stat().st_mtime > cutoff:
yield f
python复制class CustomPath(Path):
_flavour = Path._flavour # 继承默认路径风格
def read_last_line(self):
with self.open("r") as f:
for line in f:
pass
return line.strip()
# 使用自定义类
p = CustomPath("/sd/log.txt")
print(p.read_last_line())
在实际项目中,我发现这个库最实用的特性是路径拼接的运算符重载(/),它让代码比用os.path.join()更直观。特别是在处理多层嵌套的传感器数据目录时,代码可读性大幅提升。