《超简单:用 Python 让 Excel 飞起来》读书笔记:9.1.2 导入并调用 Python 自定义函数
《超简单:用 Python 让 Excel 飞起来》读书笔记:9.1.2 导入并调用 Python 自定义函数
- 1. 9.1.2 导入并调用 Python 自定义函数
- 2. 先讲清楚:RunPython 和 UDF 到底有什么区别?
- 3. 适用场景:什么时候应该用 RunPython?
- 4. 用 quickstart 创建项目:自动生成 xlsm 和 py
- 5. Python 端:写 main(),让它负责完整处理流程
- 6. VBA 端:用 RunPython 调用 Python 函数
- 7. 常见问题与踩坑记录:RunPython 三大坑位
- 7.1 模块名错误:ModuleNotFoundError
- 7.2 解释器错误:Excel 调用的 Python 不是你装包的那个
- 7.3 宏安全策略:按钮点了但没有任何反应
- 8. 效果验证:不要只看“没有报错”
- 8.1 VBA 是否成功触发 Python
- 8.2 Python 是否正确读取数据
- 8.3 Report 是否正确生成
- 9. 我的总结提升
- 10. 发布版补充:我建议这样使用这篇笔记
1. 9.1.2 导入并调用 Python 自定义函数
V4修正版进度标记:本篇为 07月15日 第2篇,承接前面 Excel 自动化基础内容继续更新。
读书笔记定位:本文是我阅读《超简单:用 Python 让 Excel 飞起来》时整理的学习笔记,重点不是照搬原书,而是把本节内容重新整理成更适合博客阅读、实操复盘和后续查阅的版本。
这一节要解决的核心问题很明确:能不能在 Excel 里点一下按钮,然后让 Python 自动完成整套数据处理流程?
前面学习 xlwings 时,我们更多是在理解 Python 如何读取 Excel、写入 Excel,或者把 Python 函数包装成 Excel 公式。但到了这一节,重点就变成了另一种更贴近办公自动化交付的方式:用 VBA 作为触发入口,用 Python 作为处理引擎,用 Excel 作为结果展示界面。
这张图展示了 Excel 按钮触发后,VBA、xlwings、RunPython 和 Python 代码之间的协作关系。
从这张图中我们可以看出,RunPython 的价值不是“在 Excel 里写一行公式”,而是把 Excel 变成一个操作面板:用户只负责点击按钮,真正的数据清洗、统计处理、报表生成交给 Python 完成。
注意:如果只把 RunPython 当成普通函数调用来理解,就会低估它的实际价值。它真正适合的是“流程型任务”,比如批量整理数据、生成报表、写入结果 Sheet、导出文件等。
建议阅读方式:
- 先理解 RunPython 和 UDF 的区别
- 再看项目如何创建、Python 端如何写 main()
- 最后重点看常见坑:模块名、解释器、宏安全
关键词:Python、Excel自动化、xlwings、VBA、RunPython、自定义函数、办公自动化、xlsm
2. 先讲清楚:RunPython 和 UDF 到底有什么区别?
很多新手第一次接触 xlwings 时,会把 UDF 和 RunPython 混在一起。这个地方必须先讲清楚,否则后面的代码会越看越乱。
UDF的定位更像 Excel 公式。比如你在单元格里写:
=MYFUNC(A1)它的使用方式和普通 Excel 函数类似,输入一个值,返回一个结果,适合做单元格级别的计算。
RunPython的定位更像“自动化任务入口”。它通常由 VBA 宏、按钮、菜单触发,执行的是一整套 Python 流程,例如读取数据、清洗数据、生成报表、写回结果。
这张图展示了 RunPython 与 UDF 的核心区别:左侧是公式计算,右侧是流程自动化。
从这张图中我们可以看出,UDF 更适合“单点计算”,RunPython 更适合“流程执行”。如果你的需求只是根据 A1 计算 B1,用 UDF 就够了;但如果你要一键处理一整张表、一整个工作簿,RunPython 更合适。
| 对比项 | UDF | RunPython |
|---|---|---|
| 使用方式 | 单元格公式 | VBA 宏 / 按钮触发 |
| 适用场景 | 单值计算、公式扩展 | 批量处理、流程自动化 |
| 典型入口 | =MYFUNC(A1) | RunPython "import xxx; xxx.main()" |
| 结果输出 | 返回到单元格 | 可写回多个 Sheet / 文件 |
| 更适合谁 | 想扩展 Excel 函数的人 | 想做自动化报表的人 |
我的建议:新手不要一上来就纠结哪个更高级,而是先判断任务类型。只要任务是“按按钮跑流程”,优先考虑 RunPython;只要任务是“像公式一样算结果”,优先考虑 UDF。
3. 适用场景:什么时候应该用 RunPython?
RunPython 最适合的不是“写一段炫技代码”,而是把重复性的 Excel 工作变成稳定流程。尤其是那些手工操作步骤多、容易出错、每周每月都要重复做的表,非常适合用 RunPython 改造。
常见场景包括:
- 一键生成报表:从原始数据 Sheet 读取数据,清洗后写入 Report Sheet;
- 批量数据处理:多个工作表统一汇总、去重、筛选、统计;
- 模板化交付:同事只打开 Excel,点按钮即可生成结果;
- 复杂逻辑计算:Excel 公式难维护,但 Python 代码更清晰;
- 图表与文件输出:生成 PNG、PDF、HTML 报告,再回写路径或结果。
不推荐的场景:如果只是两个单元格相加、简单文本拼接、普通 VLOOKUP 能解决的问题,没必要强行上 RunPython。工具不是越复杂越好,能稳定解决问题才是目标。
从办公自动化角度看,RunPython 的意义在于把 Excel 从“手工操作工具”升级为“流程触发入口”。
4. 用 quickstart 创建项目:自动生成 xlsm 和 py
要让 RunPython 跑起来,最省事的方法不是自己从零配置,而是使用 xlwings 提供的 quickstart 命令。
在目标文件夹中打开命令行,执行下面的命令:
xlwings quickstart vba_runpython_demo执行后,通常会生成一个项目文件夹,里面包含一个 Excel 宏工作簿和一个 Python 脚本文件:
vba_runpython_demo/ ├── vba_runpython_demo.xlsm └── vba_runpython_demo.py这张图展示了 quickstart 命令执行后,自动创建 `.xlsm` 和 `.py` 文件的效果。
这一步的核心要点是:让 Excel 宏文件和 Python 脚本文件保持在同一个目录下。这样 VBA 调用 Python 时,模块导入路径最简单,后续排错成本也最低。
这里不要随便改文件名。比如 Python 文件叫 `vba_runpython_demo.py`,那么 VBA 里导入模块时也应该写 `import vba_runpython_demo`。文件名、模块名、导入名不一致,是 RunPython 新手最常见的错误之一。
5. Python 端:写 main(),让它负责完整处理流程
在 RunPython 场景里,我建议给 Python 脚本写一个明确的入口函数,例如 `main()`。这样 VBA 端只负责调用 `main()`,具体逻辑都放在 Python 里维护。
一个标准的 Python 处理流程通常包括三步:
- 从 Excel 读取数据;
- 使用 Python 进行统计、清洗、汇总;
- 把结果写回 Excel 的 Report 表。
这张图展示了 Python 端 `main()` 的处理流程:左侧读取原始数据,中间执行 Python 统计处理,右侧写入 Report 结果表。
从这张图中我们可以看出,`main()` 不应该只是随便放几行代码,而应该承担“流程入口”的职责。它负责组织整个任务,让代码从读取数据到输出结果形成闭环。
下面是一份可直接理解的示例:
importxlwingsasxwdefsummarize_range(book:xw.Book,sheet_name:str,addr:str):""" 读取指定 Sheet 的指定区域,并计算总和、平均值、最大值、最小值。 """sht=book.sheets[sheet_name]values=sht.range(addr).value# 把 Excel 区域值统一展开成一维列表flat=[]ifisinstance(values,list):forrowinvalues:ifisinstance(row,list):flat.extend(row)else:flat.append(row)else:flat=[values]nums=[]forvinflat:try:ifvisNoneorv=="":continuenums.append(float(v))exceptException:passifnotnums:returnNonereturn{"count":len(nums),"sum":sum(nums),"avg":sum(nums)/len(nums),"max":max(nums),"min":min(nums),}defwrite_report(book:xw.Book,out_sheet:str,result:dict):""" 将统计结果写回 Excel 的 Report 工作表。 """sht=book.sheets[out_sheet]sht.range("A1").value=[["指标","值"],["数量 count",result["count"]],["总和 sum",result["sum"]],["平均 avg",result["avg"]],["最大 max",result["max"]],["最小 min",result["min"]],]defmain():""" VBA 调用 RunPython 时,推荐统一调用 main()。 """wb=xw.Book.caller()src_sheet="Sheet1"src_range="A1:A20"out_sheet="Report"# 如果没有 Report 表,则自动创建ifout_sheetnotin[s.nameforsinwb.sheets]:wb.sheets.add(out_sheet)result=summarize_range(wb,src_sheet,src_range)ifresultisNone:wb.sheets[out_sheet].range("A1").value="没有可用数值数据"returnwrite_report(wb,out_sheet,result)这段代码的关键点在于 `xw.Book.caller()`,它代表当前从 Excel 调用 Python 的工作簿对象。也就是说,Python 不需要重新打开 Excel 文件,而是直接接管当前正在运行宏的这个工作簿。
推荐做法:把复杂逻辑拆成多个函数,例如 `summarize_range()`、`write_report()`,最后由 `main()` 统一调度。这样后续维护和扩展会轻松很多。
6. VBA 端:用 RunPython 调用 Python 函数
Python 端写好以后,就需要在 Excel 的 VBA 里调用它。操作入口是:
Alt + F11 → 打开 VBA 编辑器 → 插入模块 → 编写宏最常用的写法如下:
Option Explicit Sub RunPythonMain() RunPython "import vba_runpython_demo; vba_runpython_demo.main()" End Sub这行代码里有两个关键点:
import vba_runpython_demo:导入同目录下的 Python 模块;vba_runpython_demo.main():调用 Python 文件里的main()函数。
注意模块名:如果你的 Python 文件叫 `demo.py`,这里就应该写 `import demo; demo.main()`。如果文件名和模块名对不上,很容易出现 `ModuleNotFoundError`。
如果你想调用 Python 里的某个单独函数,也可以这样写:
defhello(name="YJlio"):wb=xw.Book.caller()wb.sheets[0].range("D1").value=f"Hello,{name}!"VBA 端对应写法:
Sub RunPythonHello() RunPython "import vba_runpython_demo; vba_runpython_demo.hello('YJlio')" End Sub实际做项目时,我更推荐先固定一个 `main()` 作为入口。按钮只调用 main(),具体业务逻辑全部放到 Python 里。这样 Excel 端会更干净,Python 端也更容易版本管理。
7. 常见问题与踩坑记录:RunPython 三大坑位
RunPython 本身不难,真正难的是环境、路径、宏安全这些外围配置。很多时候代码没有错,但就是跑不起来,问题往往出在下面三个位置。
这张图展示了 RunPython 最容易翻车的三大坑位:模块名、解释器、宏安全。
从这张图中我们可以看出,RunPython 的排错不能只盯着 Python 代码本身,还要同时检查 Excel 宏环境、Python 解释器路径、模块导入路径。
7.1 模块名错误:ModuleNotFoundError
常见报错类似:
ModuleNotFoundError: No module named 'vba_runpython_demo'常见原因:
.xlsm和.py不在同一个目录;- Python 文件名改了,但 VBA 里的 import 没改;
- Python 文件名包含特殊字符或空格;
- 当前路径没有被加入 Python 搜索路径。
推荐处理:新手阶段尽量保持.xlsm和.py同目录,并且不要随意改名。
7.2 解释器错误:Excel 调用的 Python 不是你装包的那个
常见报错类似:
No module named xlwings No module named pandas这类错误不是包真的没装,而是 Excel 调用的 Python 解释器和你命令行里装包的解释器不是同一个。
处理思路:
- 在命令行确认当前 Python 路径:
where python python-c"import sys; print(sys.executable)"- 在 xlwings 配置中确认 Excel 使用的解释器路径;
- 把解释器改成你真正安装了
xlwings、pandas的那个环境。
不要盲目重复 pip install。先确认 Excel 调用的是哪个 Python,否则你可能一直把包装到 A 环境,而 Excel 却在用 B 环境。
7.3 宏安全策略:按钮点了但没有任何反应
在公司电脑上,宏安全经常是 RunPython 跑不起来的原因之一。尤其是从外部下载、拷贝、邮件接收的 `.xlsm` 文件,可能会被 Excel 阻止宏运行。
可以检查:
- 文件是否被标记为来自 Internet;
- Excel 宏是否启用;
- 文件是否位于受信任位置;
- 公司是否有组策略禁用了宏;
- 是否需要 IT 侧放行。
企业环境不要随便降低宏安全级别。正确做法是确认文件来源、放入受信任位置,或者按照公司安全规范申请白名单。
8. 效果验证:不要只看“没有报错”
很多人写自动化脚本,只要运行没有报错,就认为成功了。这个判断太粗糙。RunPython 的验证至少要看三个层面:
8.1 VBA 是否成功触发 Python
可以在main()里先写一个最小测试:
defmain():wb=xw.Book.caller()wb.sheets[0].range("A1").value="RunPython 调用成功"如果 A1 能写入内容,说明 VBA 到 Python 的调用链路通了。
8.2 Python 是否正确读取数据
可以临时把读取到的数据写到另一个单元格,确认读取范围没有错。
sht.range("H1").value=values8.3 Report 是否正确生成
最后要检查 Report 表是否存在,统计结果是否符合原始数据,尤其是总和、平均值、最大值、最小值是否准确。
成功标准:
- 点击按钮后没有报错;
- 指定 Sheet 的数据被正确读取;
- Report 表自动生成或更新;
- 统计结果和手工计算一致;
- 再次运行不会重复制造脏数据。
真正可靠的自动化,不是跑一次成功,而是反复运行仍然稳定。
9. 我的总结提升
这一节的核心,不是简单记住 `RunPython` 这一句 VBA 代码,而是要理解它在 Excel 自动化体系里的位置。
我对 RunPython 的定位是:Excel 负责交互,VBA 负责触发,Python 负责处理,Report 负责交付。
这和普通 Excel 脚本不一样。普通脚本可能只是帮你少点几下鼠标,而 RunPython 更适合做成一个稳定的“小型自动化系统”。
我建议重点记住这几条:
- UDF 是公式思维,RunPython 是流程思维;
- quickstart 能降低项目创建成本;
- Python 端建议统一写 main() 作为入口;
- VBA 端只负责触发,不要堆复杂业务逻辑;
- 模块名、解释器、宏安全是三大排错重点;
- 验证结果不能只看有没有报错,还要看数据是否正确写回。
如果只会复制 `RunPython "import xxx; xxx.main()"`,但不知道 Excel、VBA、Python、解释器、宏安全之间的关系,真实办公环境里一定会卡住。
所以我更建议把这一节当成“Excel 自动化交付方式”的升级点,而不是单纯当成一个语法点。
10. 发布版补充:我建议这样使用这篇笔记
这一节在书中的位置可以理解为第 9 章里从“Python 与 Excel 互通”走向“Excel 触发 Python 自动化”的关键节点。它不适合只做概念阅读,更适合边看边搭一个最小项目。
最稳的学习路径是:先用 `xlwings quickstart` 生成项目,再写一个最简单的 `main()`,最后用 VBA 调用它。等这条链路跑通后,再逐步加入读取数据、统计处理、写回 Report 的逻辑。
最不推荐的做法是直接复制一大段复杂代码,然后在报错时不知道是 VBA 错、Python 错、路径错、解释器错,还是宏安全拦截。这样排错会非常痛苦。
一句话收尾:RunPython 的本质,是把 Python 变成 Excel 里的自动化引擎。
返回顶部
