大家好,我是程序员晚枫。

今天(2026 年 10 月 26 日)开源项目 python-office 上了 1.1.0。

这次更新最值得说的一件事:python-office 终于有命令行了。

完整官方文档(含中英文):https://www.python-office.com/about/v1.1.0-release
项目地址:https://github.com/CoderWanFeng/python-office


一句话总结这次更新

变更内容
🆕 命令行 po7 大模块 42 个子命令,不写 Python 也能用
🆕 5 个 Excel 方法差异比对、去重、列画像、转 JSON、转 CSV
⚡ 懒加载优化import office 不再拖入整套依赖树

安装完直接就有:

1
2
po --version
# po 1.1.0 (python-office)

为什么我要做这个命令行

python-office 从 2022 年发布到现在,收到的最多的一类反馈是:

「我不会写 Python,能不能不写代码就用?」

我的回答一直是「可以用 AI 帮你写」。但这有个前提:你得有个 Python 环境,得会打开终端,得能读懂报错。

这次我想试试另一条路:直接把能力做成命令,交给终端、交给脚本、交给 AI Agent。

安装 python-office 之后,po 这个命令就在你终端里了:

1
2
3
4
5
6
7
8
# 合并 PDF
po pdf merge2pdf 报告A.pdf 报告B.pdf --output-file 合并报告.pdf

# Excel 转 PDF
po excel excel2pdf 报表.xlsx 报表.pdf

# 查域名解析到哪个 IP
po tools url2ip www.python-office.com

完整命令树(7 模块 42 个子命令):

1
2
3
4
5
6
7
8
po
├── pdf (11) 转换、合并、拆分、加解密、水印
├── excel (7) 模拟数据、合并拆分、查数据、转 PDF
├── file (8) 批量重命名、搜索、按名分组
├── image (8) 压缩、水印、词云、二维码、铅笔画
├── tools (6) 翻译、域名转 IP、密码、生成文章
├── markdown (1) Excel 转 Markdown
└── finance (1) 股票 T+0 收益计算

每个命令都能自己查帮助:

1
2
3
po --help                  # 全部 7 个模块
po pdf --help # pdf 的 11 个子命令
po pdf merge2pdf --help # 某个命令的全部参数

这次最被低估的设计:--json

说实话,做这个命令行时,我一开始以为大家会用 po pdf merge2pdf 这种直接调用。

但做完之后我越来越觉得,真正值钱的不是命令本身,是 --json。

1
po --json tools url2ip www.python-office.com

输出只有一行:

1
{"ok": true, "result": "1.2.3.4"}

出错也是一行,退出码 1:

1
{"ok": false, "error": "FileNotFoundError: [Errno 2] No such file or directory: 'a.pdf'"}

一个容易漏掉的坑

python-office 内部的库函数会自己 print(进度条之类的)。如果不处理,输出就会变成这样:

1
2
rows: 100%|██████████| 5/5
{"ok": true, "result": null}

脚本解析直接崩。

po 的处理办法是:库内部输出全部丢到 stderr,stdout 只留那一行 JSON。

1
2
stdout:  {"ok": true, "result": null}      ← 只有这一行
stderr: rows: 100%|██████████| 5/5 ... ← 进度条在这里

这个边界很多项目会漏,但它是「命令行能不能被脚本可靠调用」的分水岭。

为什么这对 AI Agent 特别重要

现在的 AI Agent(Claude Code、Cursor、Codex)跑命令时,最怕输出不结构化。要么解析失败,要么把进度条当成结果。

po --json 天然就是给 Agent 用的:

Agent 的需求po --json 怎么满足
判断成功还是失败看 ok 字段,不用去猜错误文本
拿结果看 result 字段
知道为什么错看 error 字段
判断要不要继续看退出码(成功 0 / 失败 1)

所以现在 python-office 的 81 个 Skill,都有了统一的命令行入口。

你要是正在搭 Agent 工作流,这条是真能用的。


另一个大改进:import office 变快了

这个改动用户可能感觉不到,但它影响很大。

改之前

1
import office

这一行会把全部 14 个 API 模块的依赖树一起拖进来。poexcel、popdf、povideo、poocr、pospider 全都加载。

你只想处理 PDF,但得把一整套都装上、全都启动一遍。

改之后

Python 3.7 以上走 PEP 562,改成按需加载:

1
2
import office          # 此时不加载任何 po* 子库
office.pdf # 访问属性时,才 import office.api.pdf → popdf

我本地实测(Python 3.12):

1
2
import office 之后加载的 po* 模块:[]          ← 空的
访问 office.pdf 之后:popdf, pofile, ... ← 按需加载

实际收益

收益说明
安装体积只用 PDF 的用户不用装 povideo / poocr / pospider 这类重包
启动速度import office 不再是「等半天」
Docker 镜像核心 CLI 镜像只要 8 个子库;全量依赖里有 moviepy、paddleocr,装一次好几分钟
兼容性Python 3.6 没 PEP 562,自动回退老逻辑,行为不变

一个我要提醒你的副作用

office/__init__.py 里不再有真实的 from office.api import ... 语句了。

这意味着你 IDE 里的「跳转定义」和自动补全会退化。

如果你平时在 VS Code / PyCharm 里靠 office.pdf 补全提示,升级后可能会发现跳转不灵了。

这是这次改造的隐性代价,我在合入 PR 时专门写在了评审意见里,也留了退路。如果社区反馈太影响体验,可以把懒加载下沉到 office/cli/ 内部,office/__init__.py 保持原样。

升级之后建议先验证一下你的 IDE。


顺手补齐的 5 个 Excel 方法

这次另一个贡献者 my12yyds 补齐了「表格处理」链条里缺的一环。

之前的方法主要解决格式转换与合并,这次补上的是比对、去重、质量统计:

方法作用和老方法的区别
excel_diff比对两份 Excelmerge2excel 是合并,这个是找差异
excel_dedupe按列或整行去重老方法没有重复检测能力
excel_column_stats逐列画像find_excel_data 按关键词找内容,这个按列做画像
excel2json工作表转 JSONexcel2markdown 输出给人看,这个输出给程序读
excel2csv导出 CSVexcel2pdf 输出 PDF,这个输出纯文本

全部只依赖 openpyxl,没引入任何新的第三方包。

excel_diff:主键配对这个设计很讲究

1
2
3
4
5
6
7
8
9
from skills.excel import excel_diff

result = excel_diff(
before_path='上周报表.xlsx',
after_path='本周报表.xlsx',
key_column='订单号',
)

print(result['summary'])

只按行号比对有个致命问题:中间插一行,后面所有行都被标成差异。

按订单号、学号、工号这类唯一编号配对,行顺序变了也不产生噪音。

而且,主键重复或为空会直接报错,不会静默给你一份错的比对结论。

这个取舍很专业,评审时我专门点了出来。

excel2csv:默认 utf-8-sig 解决中文乱码

1
2
3
from skills.excel import excel2csv

result = excel2csv(excel_path='报表.xlsx', output_dir='./csv')

默认编码是 utf-8-sig(带 BOM)。

这是中文用户最高频的坑:存成普通 UTF-8 再用 Excel 双击打开,乱码;加 BOM 就正常。

要给 Linux 工具链用的话,显式传 encoding='utf-8'。

excel_dedupe:返回明细而不是一个数字

1
2
3
4
5
6
from skills.excel import excel_dedupe

result = excel_dedupe(excel_path='客户名单.xlsx', key_columns='手机号')

for dup in result['duplicates']:
print(dup['row'], '与', dup['kept_row'], '重复')

排查问题的时候你需要知道「第 5 行和第 2 行重复」,光给个 duplicate_count 是没法用的。

⚠️ 一个要说清楚的事:这 6 个贡献者 Skill(batch_rename_by_excel + 上面 5 个)的实现代码在仓库的 contributors/ 目录,这个目录不随 pip 安装包一起发布。从 PyPI 装完调用它们,会得到一条很明确的 ImportError 提示,让你克隆源码仓库或直接复制文件过去用。这是贡献隔离模式的固有限制,不是 bug。


顺便说说这次的协作过程

1.1.0 的功能全部来自社区贡献者。两个 PR 的过程里,有几个点我觉得挺值得记录。

PR #184:CLI 的设计取舍

贡献者 Edwin.JH.Lee 提的第一波 CLI,用的是分波推进:本波先跑通 7 个核心模块 42 个子命令,其余的(video / web / word / ppt / wechat 共 20 个命令)后面补。

因为注册表结构已经就位,加命令只是加数据条目。

贡献者自建的测试基建,是这个 PR 最有价值的部分

测试层数量说明
pytest tests/test_code/test_cli.py37 passed内置 po* stub,不装重型依赖也能跑 CI
.x-cmd/test.sh real 模式15/15真转换 + 产物校验(txt2pdf→merge2pdf 页数、excel2markdown 表格、url2ip 真实 DNS)
.x-cmd/test.sh stub 模式10/10零依赖环境自动回退

还附了一个 python:3.10-slim 的 Docker 镜像,apt 和 pip 都走 aliyun 镜像。

这里有个细节我要夸:他专门写了一条用例,验证「库函数自己 print 时 stdout 仍然干净」。这个边界很多项目会漏。

评审意见里最值得留档的一条

关于 PEP 562 懒加载,我在合入评论里留了个保留意见:

这个改造的收益是实打实的——import office 不再拖入 poexcel/popdf/povideo 这类重型依赖树,对 CLI 启动速度和安装体积都有帮助。但它有一个隐性代价:__init__.py 里不再有真实的 from office.api import ... 语句,IDE 的「跳转定义」「自动补全」会退化。

处理方式是先按贡献者的方案合入,把代价记在评论里,同时给出退路:如果社区反馈跳转体验下降,可以把懒加载下沉到 office/cli/ 内部,core 目录保持原样。

PR #185:贡献者自己补上了上次的漏

#183 合入的时候,我发现贡献者加了 skills/file/batch_rename_by_excel/ 目录,但没在父包 skills/file/__init__.py 里注册。结果 from skills.file import batch_rename_by_excel 会报 ImportError,跟同目录其他 Skill 的导出方式不一致。

我补了一个 chore(skills) 提交完成注册,评论里也说了「如果你希望这条注册由你自己提交,可以把这个提交 revert 掉重提」。

到了 #185,贡献者主动在父包 skills/excel/__init__.py 注册了,而且严格只在末尾追加、不动既有行。

上次漏掉的那一步,这次他自己补上了。这种「上一轮的反馈被认真吸收」,比代码本身更让我觉得社区在往好的方向走。

一个连续踩了三次的坑

#183 和 #185 的 6 个 Skill 都用了 try/except 兜底,处理「contributors/ 目录不随包发布」的情况。但写法有问题:

1
2
3
except ImportError as _import_error:
def xxx(*args, **kwargs):
raise ImportError('...') from _import_error

Python 3 在 except 块结束后会自动 del 掉 as 绑定的异常名,闭包里引用的 _import_error 就成了未定义名字。

真实 pip 安装环境(contributors 目录不存在)下调用,实际抛出的是:

1
NameError: name '_import_error' is not defined

而不是作者预期的、带着明确指引的 ImportError。

修复方式是把异常存到模块级变量:

1
2
3
4
5
except ImportError as _import_error:
_CONTRIBUTORS_IMPORT_ERROR = _import_error

def xxx(*args, **kwargs):
raise ImportError('...') from _CONTRIBUTORS_IMPORT_ERROR

回归测试新增在 tests/test_code/test_contributors_skill_fallback.py,显式 mock 掉 contributors 导入来触发兜底分支。

这个分支在源码仓库里永远不会自然触发,所以常规测试根本抓不到。把修复回退后,新测试的 12 个 subtest 全部失败。

一条项目级的规矩

从 #181 / #182 的处理能看出来,python-office 现在的规矩是:改动 office/ 等核心目录的贡献,需要先开 issue 讨论,不能直接提 PR。 隔离式贡献(contributors/ 目录 + skills/ 包装)的可以直接提。

这条规矩的价值是:核心目录不会被「顺手改一下」的 PR 反复扰动,同时新贡献者也不用担心一不小心改坏主线。


怎么升级

1
pip install python-office --upgrade

国内网络建议走镜像:

1
pip install -i https://mirrors.aliyun.com/pypi/simple python-office --upgrade

验证一下:

1
2
3
4
5
po --version
# po 1.1.0 (python-office)

po --help
# 7 个模块,42 个子命令

只想用 CLI 不想要全套依赖的话:

1
pip install python-office popdf poexcel poimage pofile wftools pomarkdown

所有用法速查

用法场景示例
po 命令终端里直接干活po pdf merge2pdf a.pdf b.pdf
po --json脚本 / CI / AI Agent 调用po --json tools url2ip example.com
python -m office等价入口python -m office --help
from skills.X import Y现有 Python 用法from skills.pdf import pdf2docx
import office现有 Python 用法office.pdf.pdf2docx()

最后

python-office 从 2022 年发布到现在四年,这次是第一次真正的大改。

一个 Python 库,现在多了命令行,也多了被 Agent 直接调用的入口。

功能上它还是那个一句话搞定的 python-office,能触达的人变多了。

如果你想一起参与:

我一般下午 merge 一次。


参考链接

相关阅读

🎓 AI 编程实战课程

程序员晚枫专注AI编程培训,通过 《50讲 · AI编程训练营》,让小白也能用AI做出实际项目。帮你从零上手!