普贤教程 › 生态包
二进制与归档
六个处理「字节与结构化数据」的包:二进制打包、BSON、Protocol Buffers、tar、tar.gz、Java properties。它们的共同点是返回值形态不统一——同样叫「打包」,有的返 bytes、有的返 string、有的返 Result,混用就是 R1002。
bytes_pack:二进制打包
心智对齐 Python 的 struct:格式串描述字段,一次打包多个值。
px
import bytes_pack var b = bp_pack(">HI", [1, 70000]).ok() # 大端:unsigned short + unsigned int print(bytes_len(b), bp_size(">HI").ok()) # 6 6 —— 格式串本身也能算长度 print(str(bp_unpack(">HI", b).ok())) # [1, 70000]
text
6 6 [1, 70000]
格式字符:> 大端、< 小端、b/B 有符号/无符号字节、h/H 16 位、i/I 32 位、q/Q 64 位、f/d 浮点、Ns 定长字符串。
同一个包里存在两种类型
bp_pack 返回 Ok(bytes),但 bp_pack_u16_le / bp_pack_u16_be / bp_pack_u32_le / bp_pack_u32_be / bp_pack_string 返回的是 string(按 latin-1 承载字节),不是 bytes。对它们调 bytes_to_str() 会得到:
R1002: bytes_to_str 需要 bytes,实际是 string。这两组函数直接 len() 用即可。px
var s = bp_pack_u16_be(258) # string,长度 2 print(len(s), bp_unpack_u16_be(s, 0)) # 2 258 print(bp_unpack_u32_le(bp_pack_u32_le(70000), 0)) # 70000 var p = bp_pack_string("hi") # 4 字节长度前缀 + 内容 print(len(p), bp_unpack_string(p, 0)["str"]) # 6 hi
text
2 258 70000 6 hi
越界是 Err 不是回绕——这一点比 C 安全:
px
print(bp_pack(">I", [7000000000]).is_err()) # true(uint32 装不下 70 亿)
bson:和 MongoDB 同一套字节
px
import bson var doc = json_parse('{"name":"晨曦","age":30,"ok":true}') var b = bson_encode(doc).ok() print(bytes_len(b)) # 36 print(str(bson_decode(b).ok())) # {age: 30, name: 晨曦, ok: true}
text
36 {age: 30, name: 晨曦, ok: true}
数组放在字段里,编码为 BSON array 类型,解码后仍是 list:
px
print(str(bson_decode(bson_encode(json_parse('{"xs":[1,2,3]}')).ok()).ok()))
# {xs: [1, 2, 3]}字典字面量在这里不能用
PuXian 的
PuXian 的
{} 是 null,不是空字典(建空字典要 px_nd() 这类包内构造器)。所以上面一律用 json_parse 造字典,这也是全站示例的统一做法。坏输入会走 Err,不抛异常:
px
var r = bson_decode(bytes("abc")) print(r.is_err(), r.err()) # true bson: 数据过短
protobuf:不用 .proto 的 wire format
被编码的数据必须有 schema 才有字段类型,schema 本身就是 dict,键是字段号的字符串:
px
import protobuf var schema = json_parse('{"1":{"type":"int32"},"2":{"type":"string"},"3":{"type":"bool"}}') var obj = json_parse('{"1":150,"2":"hello","3":true}') var b = pb_encode(schema, obj).ok() print(bytes_len(b)) # 12 print(str(pb_decode(schema, b).ok())) # {1: 150, 2: hello, 3: true}
text
12 {1: 150, 2: hello, 3: true}
协议演进的两种典型行为,实测都对:
px
# 用旧 schema 解新数据:未知字段跳过,不报错 print(str(pb_decode(json_parse('{"1":{"type":"int32"}}'), b).ok())) # {1: 150} # 负数走 64 位补码,固定 10 字节 + 1 字节 key = 11 print(bytes_len(pb_encode(json_parse('{"1":{"type":"int32"}}'), json_parse('{"1":-1}')).ok())) # 11 print(pb_zigzag(-3), pb_unzigzag(5)) # 5 -3
pb_size() 在这个版本里恒崩它内部写的是
var data = pb_encode(...); return len(data)——对 Result 调 len(),直接
R1002: len 不支持类型 result。要用长度就自己写 bytes_len(pb_encode(schema, obj).ok())。tar:uStar 归档
入参是 list([{name, data}]),不是 dict —— 这一点和 targz 相反。
px
import tar var files = json_parse('[{"name":"a.txt","data":"hello"},{"name":"b.txt","data":"world!"}]') var buf = tar_pack(files).ok() print(bytes_len(buf)) # 3072(512 头 × 2 + 数据补齐 + 两块零尾) print(str(tar_list_files(buf))) # [a.txt, b.txt] print(tar_has_file(buf, "a.txt"), tar_get_file_size(buf, "a.txt")) # true 5 print(str(tar_unpack(buf).ok()))
text
3072 [a.txt, b.txt] true 5 [{data: hello, name: a.txt}, {data: world!, name: b.txt}]
tar_pack 返 Result,而 tar_list_files / tar_has_file / tar_get_file_size 直接返 值(list / bool / int),没有 .ok()。同一个包的函数返回值形态是混的,只能逐个试。targz:dict 入参,tar.gz 一步到位
px
import targz def main(): var d = json_parse('{"a.txt":"hello","dir/b.txt":"world!"}') var z = tz_pack(d).ok() print(str(bytes_len(z))) # 121 print(str(tz_unpack_tar(tz_gunzip(z).ok()).ok())) print(str(tz_list_files(z))) print(str(tz_has_file(z, "a.txt")) + " " + str(tz_has_file(z, "no.txt"))) print(tz_get_file(z, "dir/b.txt"))
text
121 {a.txt: hello, dir/b.txt: world!} [a.txt, dir/b.txt] true false world!
入参是 dict,不是 list
tz_tar / tz_pack 收 {路径: 内容},而 tar 包收 [{name, data}] —— 跨包搬运数据时要转向。tz_unpack_tar 解出来是 Ok({路径: 内容}),要 .ok() 一次拿到 dict;tz_list_files / tz_has_file / tz_get_file 是封装好的查询入口,直接给结果不返 Result。properties:Java .properties 解析
px
import properties var cfg = pr_parse("a=1\nb = hello\\u4e16界\n# 注释\nc:3").ok() print(str(pr_keys(cfg))) # [a, b, c] print(pr_get(cfg, "a"), pr_get(cfg, "b")) # Ok(1) Ok(hello世界) print(str(pr_merge(cfg, json_parse('{"d":"4"}'))))
text
[a, b, c] Ok(1) Ok(hello世界) {a: 1, b: hello世界, c: 3, d: 4}
: 与 = 都认,\uXXXX 会解码,# 开头是注释。取值返 Result(缺键是 Err,不是 null),要默认值用 pr_get_default。
px
pr_set(cfg, "a", "9") print(pr_to_string(cfg))
text
a=9 b=hello世界 c=3