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。

相关推荐
BU摆烂会噶1 小时前
【LangGraph】House_Agent 实战(四):预定流程 —— 中断与人工干预
android·人工智能·python·langchain
AI玫瑰助手1 小时前
Python运算符:比较运算符(等于不等等于大于小于)与返回值
android·开发语言·python
QuZhengRong1 小时前
【Luck-Report】缓存
java·前端·后端·vue·excel
GIOTTO情1 小时前
Infoseek舆情处置系统的技术实现与落地实践
python
new_dev2 小时前
Python实现Android自动化打包工具:加固、签名、多渠道一键完成
android·python·自动化
天天进步20152 小时前
从零打造 Python 全栈项目:智能教学辅助系统
开发语言·人工智能·python
带带弟弟学爬虫__2 小时前
dyAPP数据采集-个人主页、发布、搜索、评论
服务器·python·算法·flutter·java-ee·django
还是鼠鼠2 小时前
AI掘金头条新闻系统 (Toutiao News)-相关推荐
后端·python·mysql·fastapi·web
数智工坊2 小时前
PyCharm 运行 Python 脚本总自动进 Test 模式?附 RT-DETRv2 依赖缺失终极排坑
开发语言·ide·人工智能·python·pycharm