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

数据格式与编解码

29 个「输入输出」包:CSV、Excel、PDF、Markdown、HTML、YAML/TOML/INI/properties、JSONPath、multipart、SQL 解析、msgpack/protobuf/BSON、zlib/lz4、tar。共同点是纯函数、无 IO(读写文件由你调 read_file/write_file)。

CSV

px
import csv

var r = csv_parse("name,age\n张三,28\n李四,31\n")
print(r.ok())
# [[name, age], [张三, 28], [李四, 31]]

print(csv_parse("a,\"b,c\",d").ok())
# [[a, b,c, d]]        ← 引号内的逗号不切

print(csv_parse_header("a,b\n1,2").ok())
# [{a: 1, b: 2}]      ← 首行作 header,直接得 dict 列表

print(csv_serialize([["a", "b,c"], ["1", "2"]]).ok())
# a,"b,c"
# 1,2                 ← 需要时自动加引号

0.2.0 起有 csv_parse_opts(s, opts):自定义分隔符、引号符、注释符、跳空行。国内导出常遇到 ; 分隔与 BOM,BOM 要先自己 trim 掉。

Excel(xlsx)

px
import xlsx

var doc = xsx_new()
doc = xsx_add(doc, "Sheet1", [["姓名", "销量"], ["张三", 100], ["李四", 80]])
print(xsx_write(doc, "/tmp/report.xlsx"))       # Ok(0)

print(xsx_read("/tmp/report.xlsx").ok())
# [[姓名, 销量], [张三, 100], [李四, 80]]
xsx_add 返回的是新 doc,不改原对象写成 xsx_add(doc, …) 而不接返回值,写出来的 xlsx 只有 5 个骨架文件、没有 worksheet——Excel 打开是空表,且不报错。必须 doc = xsx_add(doc, …)。pdf 的 pd_add_page / pd_add_text_fs 同理。

写是「内存构建 → 一次落盘」,读是「整份进内存」。几千行没问题,十万行走不了——大表请走 CSV 或数据库导出。

PDF

px
import pdf

var doc = pd_new()
doc = pd_add_page(doc, 595, 842)                      # A4
doc = pd_add_text_fs(doc, 0, 72, 760, "月度报告", 20)
pd_save(doc, "/tmp/report.pdf")

实测落盘 593 字节,文件头 %PDF-1.4。支持文字、线、矩形、元数据;不做字体嵌入(中文需确认字体可用)、图片、表格自动排版。要复杂排版,用 HTML → 打印到 PDF。

Markdown 与 HTML

px
import markdown

print(md_render("# 标题\n\n正文 **加粗** 与 `代码`。\n\n- a\n- b\n").ok())
text
标题
正文 加粗 与 代码。
- a
- b
px
print(md_extract_links("见 [文档](https://px.dzty.club)"))   # [https://px.dzty.club]
print(md_render_html("# 标题"))        # 0.2.0 起有 HTML 输出
print(md_word_count("# 标题 正文"))     # 词数
两套 Markdown 别混用markdown 出纯文本(终端/日志用);richmd 出ANSI 彩色(终端展示用)。要 HTML 用 md_render_html。

HTML 解析

px
import html

var root = html_parse("<p>你好 <b>世界</b></p>")
print(html_text(root))                    # 你好 世界
print(html_query(root, "b")[0]["tag"])    # b
print(html_escape("<a>"))

容错解析器:标签不闭合也能出树。抓网页正文用它,别用正则。

配置格式四兄弟

px
import toml
print(toml_parse("a = 1\n[b]\nc = \"x\"\n").ok())      # {a: 1, b: {c: x}}

import ini
print(ini_parse("[db]\nhost = 127.0.0.1\n").ok())      # {: {}, db: {host: 127.0.0.1}}
# 注意:无节区键会落在空字符串键下

import dotenv
print(dotenv_parse("A=1\nB=\"x y\"\n"))                # {A: 1, B: x y}  ← 直接返回 dict,不是 Ok
print(dotenv_get(dotenv_parse("A=1"), "A"))            # 1
print(dotenv_validate(d, ["A", "B"]))                  # 必填项检查
返回形态不统一toml_parse / ini_parse / yaml_parse 返回 Ok(dict);dotenv_parse 直接返回 dict。照抄别人的代码时最容易在这里报「dict 没有 ok 方法」。

yaml 支持基础映射/序列/标量;properties 是 Java 风格;config 做的是「多层配置合并 + 点路径取值 + 类型转换」——多环境配置(默认值 ← 文件 ← 环境变量)用它最省事。

JSONPath

px
import jsonpath

var d = {"a": {"b": [10, 20, 30]}}
print(jp_get(d, "a.b[1]"))     # Ok(20)
print(jp_get(d, "a.b.1"))      # Err(jsonpath: 无键: 1)   ← 点号数字不行
print(jp_keys(d, "a"))         # [b]
数组只能用 [n]点号后面跟数字会被当成对象键名去找,返回 Err(无键)。这是最容易写错的一处。

二进制与压缩

px
import zlib
var zc = zl_compress("hello hello hello").ok()      # Ok(bytes)
print(bytes_len(zc))                                # 16
print(bytes_to_str(zl_uncompress(zc).ok()))         # hello hello hello

import lz4
var lc = lz_compress(bytes("abcabcabc")).ok()       # 入参必须是 bytes
print(bytes_to_str(lz_decompress(lc).ok()))         # abcabcabc

import msgpack
var mp = mp_pack({"a": [1, 2], "b": "中文"}).ok()
print(bytes_len(mp))                                # 15
print(mp_unpack(mp).ok())                           # {a: [1, 2], b: 中文}

import bytes_pack
print(bp_pack(">IH", [1, 65535]).ok())              # <bytes 6>
bytes 与 str 是两面len() / s[i] 在 str 上是「字符」,二进制必须用 bytes_len / bytes_get / bytes_slice。print(bytes对象) 会显示 <bytes 17>,要看内容用 bytes_to_str 或十六进制打印。

压缩选型

包典型压缩比特点
zlib高通用,HTTP 同款,解压快
lz4低极快,适合日志/中间数据
tar / targz—打包(tar_pack(files))与打包+压缩

序列化选型

包格式何时用
jsonx / 内建JSON与人/HTTP 交互(json_parse_opt)
msgpackMessagePack二进制、比 JSON 小且快
protobufProtobuf wire跨语言、有 .proto 契约
bsonBSON与 MongoDB 互操作
bytes_packstruct 风格定长二进制头(协议帧)

SQL 解析

px
import sqlparse

print(sq_parse("SELECT id, name AS n FROM users WHERE age > 18 ORDER BY id DESC LIMIT 5").ok())
# {columns: [{alias: , expr: {name: id, type: ident}}, …], distinct: false,
#  from: {alias: , table: users}, limit: 5, order_by: […], type: select, where: {…}}

SELECT 子集(含 WHERE 布尔表达式、ORDER BY、LIMIT)。用途是审计/改写 SQL——判断一条 SQL 有没有全表扫、有没有带 LIMIT;不要拿它当数据库驱动。

这一组包的通用纪律

  • 解析外部输入一律用 _opt 版本(json_parse_opt 等):抛错的版本没有 try/catch 兜底,Web 里直接 500。
  • 先判 is_err() 再取 ok(),ok() 在 Err 上返回 null,静默传下去最难查。
  • 大文件别进内存:xlsx / pdf / yaml 都是全量进内存的实现。