包使用边界
有些包的返回值形态与运行条件不符合直觉:名字看着该返字符串的地方返了 Result,该直接遍历的地方要先 .ok() 取载荷,该解释执行的地方必须编译。这页把它们集中列出,每条给可跑的调用与实测输出。
px 0.2.382,registry 195 包。下面每段输出都是脚本跑出来的原文,不是从源码推断的。速查
| 包 | 要点 | 怎么写 |
|---|---|---|
pxml | pxml_parse 返 Result,不能索引 | .is_ok() / .unwrap() |
concurrent_map | 含 mutex,解释轨不支持 | px build 后跑二进制 |
hant | 简繁对照表只有 182 个单字 | 表外字原样透传 |
tar | 归档与查元信息返 Result,其余返裸值 | 见下面第二张表 |
uuencode | uu_decode 返 bytes | bytes_to_str / uu_decode_list |
aescbc | 加解密返 bytes | 要 list[int] 用 aes_to_list |
memcached | 长度按字节声明 | 二进制值用 mc_build_set_bytes |
querybuilder | qb_build 内联转义,只有 SQL 串 | 要参数化用 qb_build_params |
dbal | 公开函数全部 db_* 前缀 | 与 querybuilder 同文件共存 |
orm | orm_from_row 按声明类型转换 | 类型名写 int / integer / varchar |
dateperiod | dp_format 返 list | 取单值用 dp_format_one / dp_format_at |
eventloop | 定时器回调可收第四参 | el_add_timer(loop, fn, delay, arg) |
finance | 摊销表三个数同号 | payment = interest + principal |
xmlstream | 读完返 null 且 xr_is_done 同时为真 | while not xr_is_done(r) 正确 |
sprintf | 宽度与零填充按 C 语义 | %08.3f → 0003.500 |
pxml:返回 Result
pxml_parse 统一到语言级 Result:成功 Ok(字典),失败 Err("line N: …")。
import std.pxml def main(): var doc = pxml_parse("name = \"web\"\nport = 8080\n") print(str(doc.is_ok())) print(str(doc.unwrap())) var bad = pxml_parse("a = @@@\n") print(str(bad.is_err())) print(bad.err())
true {name: web, port: 8080} true line 1: 无法识别的字符 '@'
Result 不能索引:写 doc["ok"] 会当场报 R1002: 此类型不支持索引: result,不会静默给 null。判成功用 is_ok(),取载荷用 unwrap(),函数内还可以写 let v = pxml_parse(t)?。JSON 序列化形态是 {"ok":bool,"value":…}:成功时载荷是配置树,失败时错误文案也在 value 里(没有 error 键),前端取错误信息要读 value。
concurrent_map:只能编译后运行
解释轨是 Mini 子集,不含 mutex 这类并发原语,所以 px run 一上来就报错:
运行时错误: 错误 [R1002] 16:33: interp 不支持 mutex(Mini 子集排除)
行号指的是包内 concurrent_map.px 第 16 行的 mutex(),不是你的脚本。换成编译执行就一切正常:
import concurrent_map def main(): var m = cm_new() cm_set(m, "a", 1) cm_set(m, "b", 2) print("values = " + str(cm_values(m))) print("string = " + str(cm_to_string(m))) print("len = " + str(cm_len(m)))
px build cm.px && ./build/cm
values = [1, 2] string = {a: 1, b: 2} len = 2
concurrent_map、spawn、chan、mutex 中任何一个,就一律 px build 之后跑二进制。托管在 Ma 的 px_routes 上的接口走的是编译缓存,不受这条限制。hant:对照表只有 182 个单字
表是一份保守子集:只收双向可逆、没有语境歧义的单字(「发」「里」「台」「干」这一类刻意不收)。表内的字照转,表外的字原样透传。
import hant def main(): print(hant_s2t("汉字学习电脑网络软件语音").ok()) print(str(hant_count_traditional("汉字学习"))) print(str(hant_count_simplified("汉字学习")))
汉字學習電脑网络软件语音 0 2
「学」「习」「电」转了,「汉」「脑」「网」「软」「件」「语」没动 —— 不是漏字,是表里本来就没有这些字。同理 t2s 把「漢」原样留着:这张表里连「漢 ⇄ 汉」这一对都没有。
判断函数用的是同一张表,所以表外字两边都不算:
import hant def main(): print("is_simplified(汉) = " + str(hant_is_simplified("汉"))) print("is_simplified(学) = " + str(hant_is_simplified("学"))) print("t2s(漢字學習) = " + hant_t2s("漢字學習").ok())
is_simplified(汉) = false is_simplified(学) = true t2s(漢字學習) = 漢字学习
hant_count_traditional 判断文本是不是繁体,也只能得出「表内繁体字有几个」,不是整段文本的语言判定。tar:两种返回值并存
同一个包里,会产生失败的操作返 Result,纯查询返裸值。
| 函数 | 返回 | 用法 |
|---|---|---|
tar_pack(files) | Result | tar_pack(files).ok() 取 bytes |
tar_unpack(buf) | Result | tar_unpack(buf).ok() 取 [{name, data}] |
tar_list_files(buf) | list | 直接遍历 |
tar_has_file(buf, name) | bool | 直接放进 if |
tar_get_file_size(buf, name) | int | 直接参与运算 |
import tar def main(): var files = [{"name": "a.txt", "data": bytes("hello")}] var r = tar_pack(files) print("pack = " + str(r)) var buf = r.ok() print("list = " + str(tar_list_files(buf))) print("has = " + str(tar_has_file(buf, "a.txt"))) print("size = " + str(tar_get_file_size(buf, "a.txt")))
pack = Ok(<bytes 2048>) list = [a.txt] has = true size = 5
tar_pack 的入参是 [{name, data}],data 写 bytes 或字符串都行(内部转 bytes)。tar_unpack 解出来是 Ok([{data: hello, name: a.txt}]) —— 键序与入参不同,按名字取。
# tar_pack(files) → Ok(bytes) 这种写法)。还是不确定,就写三行脚本 px run 一次,把 str(返回值) 打出来看 —— 比猜快。六个包的典型调用
时间、格式化、uu 编解码、日期区间、财务摊销、货币换算放在一段里跑,输出就是各函数的真实口径:
import strtotime import sprintf import uuencode import dateperiod import finance import currency def main(): var base = 1700000000 print("+1 day = " + str(st_parse_relative("+1 day", base))) print("2 days ago = " + str(st_parse_relative("2 days ago", base))) print("garbage = " + str(st_parse_relative_ok("garbage", base).is_err())) print("sprintf = " + sf_format("%08.3f", [3.5]) + " / " + sf_format("%+05d", [42])) print("uu_decode = " + bytes_to_str(uu_decode(uu_encode(bytes("hello"))))) var dp = dp_new(base, 1700172800, 86400) print("dp_format = " + json_stringify(dp_format(dp, "%Y-%m-%d"))) print("dp_one = " + dp_format_one(dp, "%Y-%m-%d")) print("amort[0] = " + json_stringify(fin_amortize(100000, 0.0049, 6)[0])) var c = cur_new({"USD": {"CNY": 7.1}}) print("cur_nested = " + str(cur_convert(c, "USD", "CNY", 100))) print("cur_missing = " + str(cur_convert_ok(c, "USD", "JPY", 100).is_err()))
+1 day = 1700086400 2 days ago = 1699827200 garbage = true sprintf = 0003.500 / +0042 uu_decode = hello dp_format = ["2023-11-14","2023-11-15","2023-11-16"] dp_one = 2023-11-14 amort[0] = {"period":1,"payment":16953.7,"interest":490,"principal":16463.7,"balance":83536.3} cur_nested = 710.0 cur_missing = true
payment = interest + principal(16953.7 = 490 + 16463.7),balance 逐期递减、末期抹平到 0。做还款计划表直接按这张表累加即可。数据库三件套:前缀分开
import dbal import querybuilder import orm def main(): var q = qb_select("users") q = qb_where(q, "age", ">", 7) print("qb = " + json_stringify(qb_build_params(q))) var d = db_new("users") d = db_where(d, "age", ">", 9) print("db = " + json_stringify(db_build(d))) var m = orm_new("users", "id") m = orm_field(m, "name", "string") m = orm_field(m, "age", "integer") print("row = " + json_stringify(orm_from_row(m, {"id": "1", "name": "甲", "age": "20"}))) print("val = " + json_stringify(orm_validate(m, {"name": "", "age": 20})))
qb = ["SELECT * FROM `users` WHERE `age` > ?",[7]] db = ["SELECT * FROM users WHERE age > ?",[9]] row = {"name":"甲","age":20} val = ["name is empty"]
dbal 的函数全部是 db_*两个包都能构造 SQL,但前缀与口径不同:querybuilder 是 qb_*(qb_select(table)、qb_build 内联转义、qb_build_params 给 [sql, params]),dbal 是 db_*(db_select(qb, columns)、db_build 一步给 [sql, params]、表名不加反引号)。同一文件里两个都 import 不会冲突;看到 qb_select 只收一个表名,就知道是 querybuilder 的。协议与流式:按字节算长度、读完置 done
import memcached import xmlstream def main(): print("mc = " + json_stringify(mc_build_set("user:1", "甲", 0, 60))) var r = xr_new("<a><b>1</b><c/></a>") var n = 0 while not xr_is_done(r): xr_read_next(r) n += 1 print("事件 = " + str(n) + ",done = " + str(xr_is_done(r)))
mc = "set user:1 0 60 3\r\n甲\r\n" 事件 = 6,done = true
mc_build_set 的第四个数是字节数存「甲」声明的是 3(UTF-8 三字节),不是 1。二进制值用 mc_build_set_bytes,读回精确切片用 mc_parse_full。xr_read_next 读到末尾返回 null 并把位置推到末尾,所以 xr_is_done 同时变真,while not xr_is_done(r) 这种写法是安全的。装包:版本必须写
pxpkg add aescbc@^0.2.0 # 写范围;裸名 pxpkg 会直接报错 grep aescbc px.pkg.lock # 看实际锁定的版本号
pxpkg add aescbc 报 非法依赖值 'aescbc'(须为 URL/路径/semver 范围)。必须写版本:@0.2.0 精确、@^0.2.0 同大版本内取最新。semver 是 node-semver 语义,主版本为 0 时次版本号就是 breaking 边界——^0.1.0 的上界是 0.2.0(开区间),要 0.2.0 就写 @^0.2.0 或 @0.2.0。