文件系统与配置
四个「跟机器打交道」的包:walk 遍历目录、fsnotify 轮询式变更检测、config 分层配置合并与环境变量覆盖、edge 操作 GPIO/I2C/串口/PWM。前三个纯软件,edge 需要真实硬件。
walk:目录遍历
import walk var root = "/data/www/px_dzty_club" print(str(wk_walk_files(root))) # [{is_dir: false, path: /…/index.html, size: 9033}, …] print(str(wk_count(root))) # {dirs: 3, files: 196, total: 199} print(str(wk_walk_ext(root, ".css", 2))) # 只取 .css,最多两层
返回的是 [{path, size, is_dir}],绝对路径,自己不排序(顺序来自 list_dir)。要稳定输出自己 sorted()。三个变体:wk_walk_files(只文件)、wk_walk_dirs(只目录)、wk_walk_ext(root, ext, max_depth)(按扩展名)。max_depth:-1 不限、0 只第一层。
fsnotify:轮询快照做变更检测
没有 inotify,靠两次快照对比。核心是 fs_snapshot → 改文件 → 再 fs_snapshot → fs_diff。
import fsnotify var before = fs_snapshot("/tmp/pxt/watchdir").ok() # 注意 .ok() write_file("/tmp/pxt/watchdir/c.txt", "3") var diff = fs_diff(before, fs_snapshot("/tmp/pxt/watchdir").ok()) print(fs_has_change(diff)) # true print(str(fsn_events(diff))) # [{kind: added, path: c.txt}]
true
[{kind: added, path: c.txt}].ok()fs_snapshot / fsn_snapshot_rec 都返回 Ok(dict),而 fs_diff 里直接
after.keys()。串起来写就是 R1007: 类型 result 没有方法 'keys'。
包头部注释里的示例(var snap = fsn_snapshot_rec(...); fs_diff(snap, …))照抄会崩,得自己补 .ok()。递归快照 fsn_snapshot_rec(dir, max_depth) 用相对路径做键(sub/file.txt),适合监控整个站点头。过滤:fsn_filter(diff, [".log"]) 按扩展名、fsn_exclude(diff, ["/cache/"]) 按子串排除。
config:分层配置
import config var base = json_parse('{"db":{"host":"127.0.0.1","port":3306},"debug":false}') var overlay = json_parse('{"db":{"host":"10.0.0.5"},"debug":true}') var cfg = cfg_merge(base, overlay) print(str(cfg)) # {db: {host: 10.0.0.5, port: 3306}, debug: true} print(cfg_get(cfg, "db.host"), cfg_get(cfg, "db.port"), cfg_get(cfg, "nope", "dft")) cfg_set(cfg, "db.port", 5432) print(str(cfg_flatten(cfg)))
{db: {host: 10.0.0.5, port: 3306}, debug: true}
10.0.0.5 3306 dft
{db.host: 10.0.0.5, db.port: 5432, debug: true}cfg_merge 是深合并:db.host 被覆盖、db.port 保留。cfg_get 走点路径,第三参是缺失时的默认值。cfg_validate(d, ["db.host","db.user"]) 返回缺失键的 list(空 list 即通过)。
cfg_apply_env_recursive(cfg, "APP") 读 APP_DB_HOST,而 cfg_cast_env 的判定是「只由
数字、-、. 组成就当数字」,于是 "10.0.0.9" 被送进 float():print(cfg_cast_env("42")) # 42 print(cfg_cast_env("true")) # true print(cfg_cast_env("hello")) # hello print(cfg_cast_env("10.0.0.9")) # 运行时错误 [R1002] 无法将 '10.0.0.9' 转为 float
PuXian 没有 try/catch,一旦配置里出现 IP、版本号
1.2.3、时间 10:30 类似形态的环境变量,
进程直接终止。用 cfg_apply_env_recursive 前先确认环境变量里没有多段小数点字符串;或者干脆绕开它,自己 env("X") + 显式转换。edge:GPIO / I2C / 串口 / PWM
它其实是 std.edge(标准库形态的 registry 包),失败语义跟别的包完全不同:不返 Result,返 -1 / false,原因查 os_errno()。参数类型错误才是致命错(直接终止)。
import edge print(exists("/dev/gpiochip0")) # 有没有这个设备 print(gpio_output("/dev/gpiochip0", 17, 1, "demo")) # -1 = 失败(第一个参数是设备路径,不是编号) print(i2c_open(1, 80)) # -1 = 失败 print(serial_open("/dev/ttyS0", 9600)) # 返回 fd;失败 -1 print(pwm_setup(0, 0, 1000000, 500000)) # false = 失败
os_errno() 不重置,别拿它判成功本机实测:
gpio_output 返 -1、os_errno()=2(ENOENT,因为容器里没有
/dev/gpiochip0);紧接着 serial_open("/dev/ttyS0") 成功返回了 fd 7,但
os_errno() 仍然是 2。判据只能是返回值本身(
-1 / false),os_errno() 只在失败之后立刻读才有意义。GPIO 用 V2 uAPI(ioctl 0xC250B403),结构体布局按 2024+ 内核;旧内核(offsets 用 u64 的 6.x 早期)布局不兼容。I2C 的寄存器读是「写 reg → stop → 读 n」两笔事务,不支持重复起始位。这几个包在普通云主机上只能验证失败路径,真板行为需硬件回归。