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

二进制与归档

六个处理「字节与结构化数据」的包:二进制打包、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 的 {} 是 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,不是 listtz_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