《超简单:用 Python 让 Excel 飞起来》读书笔记:9.2.2 手动创建文件并调用 Python 自定义函数
- 1. 9.2.2 手动创建文件并调用 Python 自定义函数
- 2. 核心原理:单元格就是参数通道
- 3. 第一步:先搭一个 Config 参数面板
- 4. 第二步:VBA 端负责写参数、校验和调度
- 5. 第三步:Python 端读取 Config 并写回结果
- 6. 第四步:把执行状态也写回 Excel
- 7. 效果验证:不要只看有没有报错
- 8. 常见问题与踩坑记录
- 8.1 Excel 没有反应,Python 没有执行
- 8.2 报错 No module named xlwings
- 8.3 Config 表名写错
- 8.4 源范围写错
- 8.5 文本数字导致统计结果不对
- 9. 总结:把脚本升级成工具,关键是可配置、可追踪、可交付
1. 9.2.2 手动创建文件并调用 Python 自定义函数
V4修正版进度标记:本篇为 07月16日 第2篇,承接 06月07日第2篇“2.7.2 自定义函数”之后继续更新。
读书笔记定位:本文是我阅读《超简单:用 Python 让 Excel 飞起来》时整理的学习笔记,重点不是照搬原书,而是把本节内容重新整理成更适合博客阅读、实操复盘和后续查阅的版本。
这一节要解决的痛点很现实:当我们把 Excel 自动化脚本交付给同事使用时,不能要求每个人都去改 Python 代码。真正好用的办公自动化工具,应该做到:业务人员改 Excel 参数,程序自动执行,结果自动回写。
如果每次换数据范围、换 Sheet、换输出位置都要打开 Python 文件改变量,那这个脚本本质上还是“作者本人专用”。更推荐的做法是把 Excel 工作表当作配置入口,让单元格成为 VBA 与 Python 之间的参数通道。
本文会围绕“Config 参数面板 + VBA 调度 + Python 执行 + 状态回写”这条链路,讲清楚如何手动创建文件,并通过 Excel 调用 Python 自定义函数。
建议阅读方式:
- 先看整体流程,理解 Excel、VBA、Python 各自负责什么
- 再看 Config 参数面板,明确参数应该如何设计
- 最后看状态回写和常见坑,避免脚本只能跑一次、不能交付
关键词:Python、Excel 自动化、xlwings、VBA、RunPython、Config 参数面板、自定义函数、办公自动化
读完这篇文章,至少要带走三件事:
- 为什么要把 Excel 单元格设计成“参数通道”;
- VBA 和 Python 在混合自动化中分别承担什么角色;
- 如何让脚本执行结果可追踪、可排错、可交付。
2. 核心原理:单元格就是参数通道
很多人第一次接触 Excel 调用 Python 时,会把注意力全部放在“Python 怎么跑起来”上。这个方向没错,但不够完整。真正可维护的自动化工具,关键不是让 Python 跑一次,而是让它能在不同输入、不同参数、不同输出场景下稳定运行。
我的理解是:Excel 负责承载数据和配置,VBA 负责调度,Python 负责计算和处理。
这套结构里最关键的设计,就是把 Excel 的某个工作表固定为 Config 参数面板。Python 不再直接写死路径、范围、Sheet 名,而是从 Config 表读取参数。
这张图展示了Excel 工作簿、参数通道、VBA 调度器、Python 计算引擎之间的完整关系。
从这张图中我们可以看出,参数通道并不是一个抽象概念,而是一个非常具体的单元格矩阵。VBA 可以把参数写入这些单元格,Python 可以从这些单元格读取配置,执行完成后再把结果写回 Excel。
这种设计最大的好处是:脚本逻辑和业务参数分离。业务参数放在 Excel 里,方便普通用户修改;核心逻辑放在 Python 里,方便开发者维护。
不要把所有参数都写死在 Python 代码里。这样后期需求一变,你就只能反复改代码,脚本很难交付给别人长期使用。
3. 第一步:先搭一个 Config 参数面板
我建议在工作簿里固定创建一个名为Config的工作表,用来集中放置运行参数。这个表不要放业务明细数据,它的职责只有一个:告诉程序应该读哪里、算什么、写到哪里。
一个最小可用的 Config 参数面板可以这样设计:
| 参数项 | 单元格 | 示例值 | 含义 |
|---|---|---|---|
| 源 Sheet | B2 | Sheet1 | Python 从哪个工作表读取数据 |
| 源范围 | B3 | A1:A20 | Python 读取哪个区域 |
| 输出 Sheet | B4 | Report | Python 把结果写到哪个工作表 |
| 输出起点 | B5 | A1 | 结果从哪个单元格开始写 |
| 模式 | B6 | summary | 执行哪种业务逻辑 |
Config 表本质上就是一个轻量级配置文件,只不过它不是 JSON、YAML 或 INI,而是 Excel 单元格。
这张图展示了在 Excel 中建立 Config 参数面板的效果,包括源 Sheet、源范围、输出 Sheet、输出起点和运行模式。
从这张图中我们可以看出,Config 参数面板的设计要尽量简单、直观。交付给同事时,不需要解释 Python 代码,只需要告诉他:以后要改输入范围、输出位置、运行模式,就改这几个格子。
我更推荐把参数项放在 A 列,参数值放在 B 列,说明放在 C 列。这样既方便用户填写,也方便后期维护文档。
Config 表名、参数位置、单元格地址必须尽量固定。如果今天放 B2:B6,明天换成 D2:D6,Python 代码就会变得很难维护。
4. 第二步:VBA 端负责写参数、校验和调度
在这套结构里,VBA 不负责复杂计算。它的定位应该很清楚:写入参数、做基础校验、调用 Python。
为什么不建议把复杂逻辑都写在 VBA 里?原因很简单:VBA 适合和 Excel 交互,但在数据清洗、文件处理、批量统计、图表生成、接口调用等方面,Python 的可维护性更强。
所以比较合理的分工是:VBA 做“按钮和调度器”,Python 做“计算引擎”。
这张图展示了VBA 调度执行的完整流程:先写入 Config 参数,再做基础校验,最后通过 RunPython 调用 Python 脚本。
这一步的核心要点是:VBA 不要只会一行RunPython。在真正交付时,应该先检查参数是否为空、Sheet 是否存在、范围是否合法,否则脚本失败后用户只会看到一堆看不懂的报错。
下面是一段可复用的 VBA 模板:
Option Explicit Sub Run_With_Config_Channel() Dim wb As Workbook Set wb = ThisWorkbook ' 1. 写入或更新 Config 参数 With wb.Worksheets("Config") .Range("B2").Value = "Sheet1" ' 源 Sheet .Range("B3").Value = "A1:A20" ' 源范围 .Range("B4").Value = "Report" ' 输出 Sheet .Range("B5").Value = "A1" ' 输出起点 .Range("B6").Value = "summary" ' 执行模式 End With ' 2. 读取关键参数并做基础校验 Dim srcSheet As String Dim srcRange As String Dim outSheet As String Dim outCell As String With wb.Worksheets("Config") srcSheet = Trim(.Range("B2").Value) srcRange = Trim(.Range("B3").Value) outSheet = Trim(.Range("B4").Value) outCell = Trim(.Range("B5").Value) End With If srcSheet = "" Or srcRange = "" Or outSheet = "" Or outCell = "" Then MsgBox "Config 参数不能为空,请检查 B2:B5。", vbExclamation Exit Sub End If ' 3. 调用 Python RunPython "import channel_demo; channel_demo.main()" MsgBox "Python 已执行完成,结果已写回 Excel。", vbInformation End Sub这段 VBA 的价值不是代码有多复杂,而是把调度流程规范化了。它明确告诉读者:先准备参数,再校验参数,最后再执行 Python。
如果直接 RunPython,不做任何参数检查,问题迟早会暴露在用户手里。尤其是企业环境中,别人不会帮你看代码,只会反馈“点了没反应”。
5. 第三步:Python 端读取 Config 并写回结果
Python 端的核心动作也很清楚:读取 Config 参数 → 执行业务逻辑 → 写回结果。
这里可以通过xlwings的xw.Book.caller()获取当前调用 Python 的 Excel 工作簿,然后读取 Config 表里的参数。
xw.Book.caller()是 Excel 调用 Python 时非常关键的入口,它让 Python 知道“是谁在调用我”。
这张图展示了Python 从 Config 工作表读取参数,执行处理逻辑,并把统计结果写回 Report 工作表的过程。
从这张图中我们可以看出,Python 并不是孤立运行的脚本,而是被 Excel 参数驱动的计算引擎。左边是 Config 输入,中间是 Python 逻辑,右边是 Report 输出结果。
下面是 Python 示例文件channel_demo.py:
importxlwingsasxwdefread_config(wb:xw.Book)->dict:"""读取 Config 工作表中的参数"""cfg=wb.sheets["Config"]return{"src_sheet":str(cfg.range("B2").value).strip(),"src_range":str(cfg.range("B3").value).strip(),"out_sheet":str(cfg.range("B4").value).strip(),"out_cell":str(cfg.range("B5").value).strip(),"mode":str(cfg.range("B6").value).strip().lower(),}defensure_sheet(wb:xw.Book,sheet_name:str)->xw.Sheet:"""如果输出工作表不存在,则自动创建"""sheet_names=[s.nameforsinwb.sheets]ifsheet_namenotinsheet_names:wb.sheets.add(sheet_name)returnwb.sheets[sheet_name]defflatten_values(values):"""把 Excel 区域读取出来的二维数据压平成一维列表"""ifnotisinstance(values,list):return[values]result=[]forrowinvalues:ifisinstance(row,list):result.extend(row)else:result.append(row)returnresultdefsummarize(values):"""对数值数据做基础统计"""nums=[]forvalueinflatten_values(values):ifvalueisNoneorvalue=="":continuetry:nums.append(float(value))exceptException:passifnotnums:returnNonereturn{"count":len(nums),"sum":sum(nums),"avg":sum(nums)/len(nums),"max":max(nums),"min":min(nums),}defmain():"""Excel 调用 Python 的主入口"""wb=xw.Book.caller()cfg=read_config(wb)src_sht=wb.sheets[cfg["src_sheet"]]data=src_sht.range(cfg["src_range"]).value out_sht=ensure_sheet(wb,cfg["out_sheet"])ifcfg["mode"]=="summary":result=summarize(data)ifresultisNone:out_sht.range(cfg["out_cell"]).value=[["没有可用数值数据"]]returnrows=[["指标","值"],["数量 count",result["count"]],["总和 sum",result["sum"]],["平均 avg",result["avg"]],["最大 max",result["max"]],["最小 min",result["min"]],]out_sht.range(cfg["out_cell"]).value=rowselse:out_sht.range(cfg["out_cell"]).value=[[f"未知模式:{cfg['mode']}"]]这段代码真正可复用的地方在于 mode 字段。现在我们只写了summary模式,后面完全可以扩展成clean、chart、pivot、export等不同模式。
不要让一个 Python 文件只能处理一种固定场景。如果你已经做了 Config 参数面板,就应该顺手给脚本留出扩展入口。
6. 第四步:把执行状态也写回 Excel
很多 Excel 自动化脚本最大的问题不是不能运行,而是失败后无法定位问题。用户点了按钮,没看到结果,也不知道是 Python 没启动、参数错了、Sheet 不存在,还是代码异常。
所以我建议把执行状态也写回 Config 表。这样 Excel 不只是输入参数的地方,也变成了一个简单的状态面板。
可以增加三个字段:
| 字段 | 建议单元格 | 作用 |
|---|---|---|
| 运行时间戳 | B7 | 记录本次脚本开始执行时间 |
| 执行状态 | B8 | Running / OK / Fail |
| 错误信息 | B9 | 记录异常摘要,方便排障 |
这张图展示了执行状态可视化的设计,包括 OK、Fail、Running、错误信息回写等关键状态。
从这张图中我们可以看出,状态回写能让脚本从“黑盒工具”变成“可追踪工具”。一旦失败,用户至少能看到失败阶段和错误摘要,后续排障成本会低很多。
Python 端可以这样封装状态回写:
importxlwingsasxwfromdatetimeimportdatetimedefset_status(wb:xw.Book,status:str,message:str=""):"""将执行状态写回 Config 表"""cfg=wb.sheets["Config"]cfg.range("B7").value=datetime.now().strftime("%Y-%m-%d %H:%M:%S")cfg.range("B8").value=status cfg.range("B9").value=messagedefmain():wb=xw.Book.caller()try:set_status(wb,"Running","")# 这里写你的正常业务逻辑cfg=read_config(wb)src_sht=wb.sheets[cfg["src_sheet"]]data=src_sht.range(cfg["src_range"]).value out_sht=ensure_sheet(wb,cfg["out_sheet"])result=summarize(data)ifresultisNone:out_sht.range(cfg["out_cell"]).value=[["没有可用数值数据"]]else:rows=[["指标","值"],["数量 count",result["count"]],["总和 sum",result["sum"]],["平均 avg",result["avg"]],["最大 max",result["max"]],["最小 min",result["min"]],]out_sht.range(cfg["out_cell"]).value=rows set_status(wb,"OK","")exceptExceptionase:set_status(wb,"Fail",str(e))raise状态回写是非常值得保留的工程化细节。它不会让代码复杂多少,但能显著提升工具的可维护性。
如果不给用户任何错误信息,自动化工具一旦失败,就会变成只能作者本人排查的“私人工具”。
7. 效果验证:不要只看有没有报错
脚本执行完成后,不能只看“有没有弹出完成提示”。真正的验证应该看三类结果:参数是否被正确读取、结果是否被正确写回、状态是否被正确更新。
我一般会按下面这个顺序验证:
| 验证项 | 预期结果 |
|---|---|
| Config 参数是否完整 | B2:B6 有源表、范围、输出表、输出起点和模式 |
| VBA 是否能正常调用 | 点击按钮后无宏报错 |
| Python 是否能读取当前工作簿 | xw.Book.caller()能正常工作 |
| Report 表是否生成 | 如果输出表不存在,应自动创建 |
| 统计结果是否正确 | count、sum、avg、max、min 与源数据一致 |
| 状态是否回写 | B8 显示 OK 或 Fail,B9 有错误摘要 |
验证结果时,建议用一组非常小的数据先跑通。例如 A1:A5 放几个简单数字,手工算一遍,再对比 Python 输出结果。
不要一上来就拿几万行真实数据测试。如果基础链路没打通,大数据只会把排查范围放大。
8. 常见问题与踩坑记录
8.1 Excel 没有反应,Python 没有执行
优先检查 xlwings 插件是否加载成功,以及 VBA 中是否能识别RunPython。
建议先用 xlwings quickstart 生成一个最小项目,确认 Excel 调 Python 的链路是通的。
8.2 报错 No module named xlwings
这个问题通常不是 xlwings 没装,而是 Excel 调用的 Python 环境和你安装 xlwings 的 Python 环境不是同一个。
本质是 Python 解释器路径不一致。可以在 xlwings 设置中检查 Interpreter,也可以在当前环境重新执行:
pipinstallxlwings8.3 Config 表名写错
如果 Python 中写的是wb.sheets["Config"],那么 Excel 里工作表名称必须完全一致。多一个空格、少一个字母都会失败。
工作表名称、参数单元格、输出范围这类信息,最好不要频繁改。
8.4 源范围写错
常见错误包括把英文冒号写成中文冒号,例如A1:A20。Excel 看起来差不多,但程序读取时可能失败。
建议所有范围地址统一使用英文符号,例如A1:A20。
8.5 文本数字导致统计结果不对
Excel 里有些数字看起来是数字,实际是文本。Python 做统计前最好统一尝试转换为float,无法转换的值跳过或单独记录。
这也是为什么上面的 summarize 函数没有直接 sum 原始数据,而是先做了数值转换。
9. 总结:把脚本升级成工具,关键是可配置、可追踪、可交付
这一节表面上讲的是“手动创建文件并调用 Python 自定义函数”,但我真正想沉淀的是一个更通用的办公自动化思路:不要只写能跑的脚本,要写能交付的工具。
这套方案可以总结为四句话:
- Config 表负责配置:让用户通过单元格修改参数;
- VBA 负责调度:写参数、做校验、调用 Python;
- Python 负责计算:读取参数、执行逻辑、写回结果;
- 状态面板负责反馈:记录 Running、OK、Fail 和错误信息。
如果一个自动化脚本只能作者本人维护,那它还不算真正完成。真正可交付的脚本,应该让别人看得懂入口、改得动参数、查得到状态、复现得了结果。
所以我建议后续写 Excel + Python 自动化工具时,都优先考虑“参数通道”这个设计。它不是复杂架构,而是把 Excel、VBA、Python 三者的职责边界划清楚。
下一篇可以继续深入:如何把 Python 函数导入 Excel,像普通 Excel 公式一样在单元格中调用。
返回顶部