px普贤教程PuXian
普贤教程 › 生态包

包使用边界

有些包的返回值形态与运行条件不符合直觉:名字看着该返字符串的地方返了 Result,该直接遍历的地方要先 .ok() 取载荷,该解释执行的地方必须编译。这页把它们集中列出,每条给可跑的调用与实测输出。

实测环境语言 px 0.2.382,registry 195 包。下面每段输出都是脚本跑出来的原文,不是从源码推断的。

速查

包要点怎么写
pxmlpxml_parse 返 Result,不能索引.is_ok() / .unwrap()
concurrent_map含 mutex,解释轨不支持px build 后跑二进制
hant简繁对照表只有 182 个单字表外字原样透传
tar归档与查元信息返 Result,其余返裸值见下面第二张表
uuencodeuu_decode 返 bytesbytes_to_str / uu_decode_list
aescbc加解密返 bytes要 list[int] 用 aes_to_list
memcached长度按字节声明二进制值用 mc_build_set_bytes
querybuilderqb_build 内联转义,只有 SQL 串要参数化用 qb_build_params
dbal公开函数全部 db_* 前缀与 querybuilder 同文件共存
ormorm_from_row 按声明类型转换类型名写 int / integer / varchar
dateperioddp_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: …")。

px
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())
text
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 一上来就报错:

text
运行时错误: 错误 [R1002] 16:33: interp 不支持 mutex(Mini 子集排除)

行号指的是包内 concurrent_map.px 第 16 行的 mutex(),不是你的脚本。换成编译执行就一切正常:

px
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)))
sh
px build cm.px && ./build/cm
text
values = [1, 2]
string = {a: 1, b: 2}
len    = 2
怎么判断该不该 build脚本里出现 concurrent_map、spawn、chan、mutex 中任何一个,就一律 px build 之后跑二进制。托管在 Ma 的 px_routes 上的接口走的是编译缓存,不受这条限制。

hant:对照表只有 182 个单字

表是一份保守子集:只收双向可逆、没有语境歧义的单字(「发」「里」「台」「干」这一类刻意不收)。表内的字照转,表外的字原样透传。

px
import hant

def main():
    print(hant_s2t("汉字学习电脑网络软件语音").ok())
    print(str(hant_count_traditional("汉字学习")))
    print(str(hant_count_simplified("汉字学习")))
text
汉字學習電脑网络软件语音
0
2

「学」「习」「电」转了,「汉」「脑」「网」「软」「件」「语」没动 —— 不是漏字,是表里本来就没有这些字。同理 t2s 把「漢」原样留着:这张表里连「漢 ⇄ 汉」这一对都没有。

判断函数用的是同一张表,所以表外字两边都不算:

px
import hant

def main():
    print("is_simplified(汉) = " + str(hant_is_simplified("汉")))
    print("is_simplified(学) = " + str(hant_is_simplified("学")))
    print("t2s(漢字學習)     = " + hant_t2s("漢字學習").ok())
text
is_simplified(汉) = false
is_simplified(学) = true
t2s(漢字學習)     = 漢字学习
别拿它做生产级简繁转换182 字的覆盖面只够「近似展示」。要真的转,自己带一份 OpenCC 数据或整表映射;反过来,用 hant_count_traditional 判断文本是不是繁体,也只能得出「表内繁体字有几个」,不是整段文本的语言判定。

tar:两种返回值并存

同一个包里,会产生失败的操作返 Result,纯查询返裸值。

函数返回用法
tar_pack(files)Resulttar_pack(files).ok() 取 bytes
tar_unpack(buf)Resulttar_unpack(buf).ok() 取 [{name, data}]
tar_list_files(buf)list直接遍历
tar_has_file(buf, name)bool直接放进 if
tar_get_file_size(buf, name)int直接参与运算
px
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")))
text
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}]) —— 键序与入参不同,按名字取。

拿不准时看包文件头registry 包里每个函数上方都标了返回类型(# tar_pack(files) → Ok(bytes) 这种写法)。还是不确定,就写三行脚本 px run 一次,把 str(返回值) 打出来看 —— 比猜快。

六个包的典型调用

时间、格式化、uu 编解码、日期区间、财务摊销、货币换算放在一段里跑,输出就是各函数的真实口径:

px
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()))
text
+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。做还款计划表直接按这张表累加即可。

数据库三件套:前缀分开

px
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})))
text
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

px
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)))
text
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) 这种写法是安全的。

装包:版本必须写

bash
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。
这份速查怎么用照上面「怎么写」那一列抄,比读源码快。各包的完整用法在 文本 / 安全 / 结构与并发 / 协议 / 数据 五篇实战页里,每篇都有可跑的完整脚本。