Python 数据分析基础入门:《Excel Python:飞速搞定数据分析与处理》学习笔记系列(第十二章 用户定义函数 上篇)

Excel Python:飞速搞定数据分析与处理

第四部分 使用 xlwings 对 Excel 应用程序进行编程

第十二章 用户定义函数

前 3 章内容展示了如何使用 Python 脚本自动化 Excel,以及如何在 Excel 中一键执行这样的脚本。本章会介绍另一种利用 xlwings 在 Excel 中调用 Python 代码的方法,即用户定义函数 (user-defined function,UDF)。 UDF 是可以用在 Excel 单元格中的 Python 函数,就像使用内置的 SUM 函数和 AVERAGE函数一样。和第 11 章一样,我们首先从 quickstart 命令开始,尝试创建第一个 UDF。然后进入案例研究,学习如何从 Google Trends 上获取和处理数据,以便处理一些更复杂的 UDF:学习如何处理 pandas DataFrame 和图表,以及如何调试 UDF。最后本章会以性能优化为中心深入了解一些高级主题。不幸的是,xlwings 在 macOS 中不支持 UDF,所以本章需要你在 Windows 中运行示例代码。

12.1 UDF 入门

在使用 quickstart 命令运行我们的第一个 UDF 之前,首先介绍一下事前准备。为了能够跟上本章中的示例,你需要安装好 xlwings 插件,并启用 Excel 的"信任 VBA 项目对象模型"选项。

信任 VBA 项目对象模型: 要编写你的第一个 UDF,需要修改 Excel 的一项设置:进入 "文件(File)>选项 (Options)> 信任中心(Trust Center)> 信任中心设置(Trust Center Settings)> 宏设置 (Macro Settings)" 菜单项中,勾选 "信任VBA项目对象模型"(Trust access to the VBA project object model),如下图所示。这样 xlwings 就可以在点击插件的 Import Function 按钮后自动将 VBA 模块插入工作簿中,很快你就会看到这样的操作是如何完成的。由于仅在导入过程中才需要这项设置,因此应将其视为最终用户无须担心的一项开发者设置。

UDF Quickstart

和 xlwings 的其他功能一样,创建 UDF 的最简单办法就是从 quickstart 命令开始。在 Anaconda Prompt 中执行下面的命令之前,务必通过 cd 命令切换到你所选的工作目录。

复制代码
(base) D:\DevTools\Jupyter\python-for-excel-1st-edition\udfs>xlwings quickstart first_udf
Error: Directory already exists.		# 这里可以删除已存在的目录后,再执行命令

从文件浏览器中进入 first_udf 文件夹,在 Excel 中打开 first_udf.xlsm 文件,同时在 VS Code 中打开 first_udf.py。然后在 xlwings 功能区插件中点击 Import Function 按钮。在默认情况下这个过程是静默的,也就是说只要没有错误你就看不到任何提示内容。不过,如果你勾选了 Excel 插件中的 Show Console(显示控制台)选项,然后再次点击 Import Functions 按钮,就会在打开的命令提示符中看到下面的内容:

第一行打印了一些细节,不过可以忽略它们。最重要的是打印出这一行内容之后,Python 就启动了。第二行确认从 first_udf 模块中正常导入了函数。现在在 first_udf.xlsm 的活动工作表的 A1 单元格中输入 =hello("xlwings"),然后按回车键,如下图所示,你会看到公式求值后的结果。

下面来逐步分析一下 UDF 的工作原理:首先看到 first_udf.py(参见例 12-1)中的 hello 函数,此前我们一直忽略了 quickstart 的这部分代码。

复制代码
例12-1 first_udf.py(节选)

import xlwings as xw 

@xw.func 
def hello(name): 
    return f"Hello {name}!"

在点击 xlwings 插件的 Import Functions 按钮时,所有被 @xw.func 标记的函数都会被导入 Excel 中。导入之后你便可以在 Excel 中将其用作单元格公式,其中的技术细节马上就会讲到。@xw.func 是一个装饰器,这就意味着你必须把它放到函数定义的上方。

函数装饰器: 装饰器是放在函数定义上面的一个函数名且开头有 @ 符号。这是一种改变函数行为的便捷方式,xlwings 利用装饰器来识别你想在 Excel 中使用的函数。为了帮助你理解装饰器的工作方式,下面的例子展示了一个叫作 verbose 的装饰器,它会在 print_hello 函数执行前和执行后打印一些文本。从技术上来说,装饰器会将函数(print_hello) 作为实参传递给 verbose 函数的 func 参数。verbose 函数内部的 wrapper 函数随后可以进行必要的工作。在本例中,就是在调用 print_hello 函数前后各打印一个值。内部函数的名称可以随意命名:

复制代码
In [1]: # 函数装饰器的定义 
        def verbose(func): 
            def wrapper(): 
                print("Before calling the function.") 
                func() 
                print("After calling the function.") 
            return wrapper 
In [2]: # 使用函数装饰器 
        @verbose 
        def print_hello(): 
            print("hello!") 
In [3]: # 调用被装饰函数的效果 
        print_hello() 
Before calling the function. 
hello! 
After calling the function.

在默认情况下,如果函数参数是单元格区域,那么 xlwings 就会将单元格区域的值而不是 xlwings 的 range 对象传递给你。在大部分时候这是很方便的,你可以利用单元格为参数来调用 hello 函数。例如,可以将 "xlwings" 写入 A2 单元格,然后将 A1 的公式改成下面这样:

复制代码
=hello(A2)

其结果和上图(表格)是一样的。12.3 节会向你展示如何修改这种行为,使得参数以 xlwings range 对象形式传递。届时你就会明白,在有些情况下需要这样做。在 VBA 中,等效的 hello 函数看起来像下面这样:

复制代码
Function hello(name As String) As String 
    hello = "Hello " & name & "!" 
End Function

点击插件上的 Import Functions 按钮之后,xlwings 会在你的 Excel 工作簿中插入一个名为 xlwings_udfs 的 VBA 模块。这个模块中保存了每一个导入的 Python 函数生成的 VBA 函数:这些 VBA 函数包装器负责执行对应的 Python 函数。虽然也可以按快捷键 Alt+F11 打开 xlwings_udfs 看一下里面的内容,但是你完全可以忽略它们,因为这些代码都是自动生成的,每次点击 Import Functions 按钮时,所有的更改都会丢失。现在来用 first_udf.py 中的 hello 函数测试一下,把返回值中的 Hello 换成 Bye:

复制代码
@xw.func 
def hello(name): 
    return f"Bye {name}!"

要在 Excel 中重新计算函数,可以双击 A1 单元格编辑公式(或者选中单元格,按 F2 键激活编辑模式),然后按回车键,结果如上图所示。也可以按键盘快捷键 Ctrl+Alt+F9:这个快捷键会强制重新计算所有已打开的工作簿中的所有工作表,且包括 hello 公式。注意,F9 键(重新计算所有已打开的工作簿中的所有工作表)或快捷键 Shift+F9(重新计算活动工作表)并不会重新计算 UDF,因为只有在依赖单元格发生改变时 Excel 才会重新计算 UDF。要改变这种行 为,可以为 func 装饰器添加对应的参数以使函数具有易失性:

复制代码
@xw.func(volatile=True) 
def hello(name): 
    return f"Bye {name}!"

每次 Excel 执行重新计算时,易失性函数都会求值,无论函数的依赖项是否发生改变。有一些 Excel 内置函数就是易失的,比如 =RAND() 或 =NOW()。大量使用这些函数会导致工作簿变慢,所以应避免过度使用。如果修改了函数名、参数,或者像上面那样修改了 func 装饰器,那么在这些情况下需要点击 Import Functions 按钮重新导入函数:Python 解释器会重启并导入更新后的函数。如果现在把 Bye 改回 Hello,则只需使用键盘快捷键 Shift+F9 或者 F9 键重新计算公式即可,因为函数已经是易失性函数了。

在默认情况下,xlwings 会在 Excel 文件所在目录从同名 Python 文件中导入函数。重命名和移动 Python 源文件需要做与第 10 章提到过的类似的更改,即和 RunPython 调用一样, 将 first_udf.py 改成 hello.py。为了让 xlwings 知道发生了改变,需要将模块名称(hello,不带 .py 扩展名)添加到 xlwings 插件的 UDF Modules 中,如下图所示。

点击 Import Functions 按钮重新导入该函数。然后重新计算 Excel 的公式,确认一切照常工作。

从多个 Python 模块中导入函数 : 如果你想从多个模块中导入函数,那么可以在 UDF Module 设置中用分号分隔多个模块名,比如 hello;another_module。

现在将 hello.py 移动到桌面上:此时你需要将桌面的路径添加到 xlwings 插件的 PYTHONPATH 中。正如在第 10 章中看到的那样,也可以通过环境变量来实现,也就是说将插件中的 PYTHONPATH 设置为 %USERPROFILE%\Desktop。如果 PYTHONPATH 中还留有第 10 章的 pyscripts 文件夹,则可以直接覆盖,或者不去管它,多个路径之间用分号分隔就行了。完成这些改动之后,再次点击 Import Functions 按钮,重新计算 Excel 中的函数,检查一切是否照常工作。

如果你修改了 UDF 的 Python 代码,则 xlwings 会在保存 Python 文件时跟进所有更改。正如前面提到的那样,如果你修改了函数名、参数、装饰器等内容,则只需要重新导入 UDF。但是如果你的源文件导入了其他模块的代码,而这些模块的内容也发生了更改,那么让 Excel 跟进所有更改的最简单办法是点击 Restart UDF Server(重启 UDF 服务器)。

现在你已经知道如何用 Python 编写一个简单的 UDF 并将其用到 Excel 中。下一节的案例研究会向你介绍运用了 pandas DataFrame 的更为真实的 UDF。

相关推荐
用户8356290780513 分钟前
使用 Python 操作 Word 评论和回复
后端·python
Zella折耳根22 分钟前
复习篇-继承和接口
java·开发语言·python
诗词在线26 分钟前
求推荐飞花令
大数据·人工智能·python
yijianace40 分钟前
Python线程与多线程完全总结(从入门到理解并发本质)
开发语言·python
会Tk矩阵群控的小木2 小时前
基于Python的iMessage短信群发与社媒多账号统一管理系统实现
开发语言·windows·python·新媒体运营·开源软件·个人开发
质造者2 小时前
LangChain + Ollama + Tavily 实现旅游问答系统
linux·人工智能·python·langchain·rag
伊布拉西莫2 小时前
【流畅的Python】第20章:并发执行器 — 学习笔记
笔记·python·学习
IT策士2 小时前
Redis 从入门到精通:Python 操作 Redis
redis·python·bootstrap
编码者卢布3 小时前
【Azure AI Search】 searchMode=any 和 searchMode=all 有什么区别?
人工智能·python·flask
Samooyou3 小时前
大模型微调(Fine Tuning)
人工智能·python·ai·语言模型