1.2 用在Excel上的Python

Excel的主要功能是存储数据、分析数据和可视化数据。而Python在科学计算方面也极其强大,天生就适合搭配 Excel工作。能同时引起专业程序员和初学者(他们可能几周只写那么几行代码)兴趣的编程语言不多,Python 便是其中一门。专业程序员喜欢 Python 是因为它是一门通用编程语言。你可以用Python 轻松地实现大部分事情。而对于初学者来说,比起其他语言,Python 显得更加简单易学。Python的用途广泛,小到即时数据分析、自动化任务,大到如 Instagram 后端的代码库,都有Python的功劳。这也就意味着当你用Python 编写的Excel工具流行起来之后,可以很容易地找到一名 Web 开发人员将你的Excel-Python 原型转化为功能齐全的Web 应用程序。Python的独特优势在于,处理业务逻辑的部分很可能不需要重写就能原封不动地从Excel原型迁移到Web 生产环境中。

本节会介绍Python的各种核心概念,并会将它们和Excel及VBA 进行对比,也会涉及可读性、Python 标准库、包管理器、科学计算栈、现代语言特性、跨平台兼容性等内容。先来了解一下可读性。

1.2.1 可读性和可维护性

当说代码“可读”时,意思是这些代码很容易理解——特别是对于那些并没有写这些代码的人来说。良好的可读性使得发现错误和维护代码更加容易,这也是为什么《Python 之禅》(The Zen of Python)中会写道“可读性很重要”(readability counts)。《Python 之禅》是对Python 核心设计原则的精辟总结,在第2章中我们会学到如何输出这首禅诗。先来看看下面的VBA 代码:

If i < 5 Then
Debug.Print "i is smaller than 5"
ElseIf i <= 10 Then
Debug.Print "i is between 5 and 10"
Else
Debug.Print "i is bigger than 10"
End If

在VBA中,可以将上面的代码整理成下面这样,前后两段代码完全等效:

If i < 5 Then
Debug.Print "i is smaller than 5"
ElseIf i <= 10 Then
Debug.Print "i is between 5 and 10"
Else
Debug.Print "i is bigger than 10"
End If

在第一个版本中,视觉上的缩进和代码逻辑一致。这使得代码易于阅读和理解,进而更容易发现其中的错误。在第二个版本中,初见这段代码的开发者可能看不到ElseIfElse条件,而如果这段代码来自更为庞大的代码库则更是如此。

Python 不会接受像上面第二个版本那样的代码,它会强制你将视觉缩进和代码逻辑对齐,从而避免可读性问题。之所以有这种强制性,是因为当你在if语句或for循环中使用代码块时,Python 依靠缩进来定义代码块。其他大多数语言用花括号而不是缩进来定义代码块,VBA 则使用End If等关键字,就像我们刚才在前面的代码中看到的那样。使用缩进定义代码块的原因在于,编程时大部分时间是花费在维护代码而不是现写新的代码上。可读性好的代码可以帮助新进程序员(也可能是写下代码几个月之后的你自己)回顾过去、了解现状。

第3章会介绍Python的缩进规则,现在先来了解一下 Python 提供的随时可用的内置功能——标准库。

1.2.2 标准库和包管理器

Python 通过标准库提供了丰富的内置工具。Python 社群喜欢称之为“自带电池”。无论是需要解压 ZIP 文件,还是从CSV 文件中读取数据,抑或想要从互联网上获取数据,Python 标准库都能给你安排妥当,并且通常只需要几行代码。如果想在VBA中实现同样的功能,则可能需要大量的代码,或是安装插件。通常你在网上找到的解决方案都只能在Windows中工作,到macOS中就不行了。

尽管 Python 标准库涵盖了大量的功能,但还是有一些功能难以编写,又或是使用标准库来实现效率很低。这个时候就该 PyPI上场了。PyPI 代表Python Package Index(Python 包目录),它是任何人(包括你!)都可以上传开源 Python 包的巨大仓库,利用这些包可以扩展 Python的功能。

 PyPI和PyPy

PyPI读作“pie pea eye”,PyPy 则读作“pie pie”。PyPy 是另一种高效的Python 实现。

如果你想更方便地从互联网上获取数据,就可以安装Requests 包来获取一系列强大又好用的命令。要安装一个包,你需要在命令提示符或者终端中使用Python的包管理器,即pip。pip 是pip installs packages的递归缩写。虽然听起来有点儿抽象,不过别担心,第2章会解释它是如何工作的。现在更重要的是理解为什么包管理器如此重要。一个主要原因是,任何优质的包可能不仅依赖于Python 标准库,还会依赖于PyPI上的其他开源包。而这些依赖项又可能会依赖其他的包,层层递进。pip 会递归地检查一个包的依赖项和子依赖项,并逐一下载安装。你还可以使用pip 轻松地更新包,以保持各个依赖项都是最新版本。pip 让你能够坚守 DRY 原则,因为不用重新发明轮子或者复制粘贴 PyPI上已有的包。有了pip和PyPI,你就有了一套统一的机制来分发和安装依赖项——这正是Excel的插件所欠缺的。

开源软件

在这里我想简单谈一下开源(open source)。本节在前面内容中已经几次提到这个词。如果一款软件依照某种开源许可证分发,那么就意味着我们可以免费、自由地获取它的源代码,并且任何人都可以参与添加新功能、修复 bug 或撰写文档。Python 本身以及大多数第三方 Python 包是开源的,大部分是由开发者在业余时间维护的。但这并不一定是一种理想状态。如果你的公司长期在用一个包,那么你会希望有专业开发者对其进行持续开发和维护。幸运的是,Python 科学计算社区已经意识到了这一点:一些包对于科学计算至关重要,如果把它们留给只在晚上和周末对其进行开发的少数志愿者,则实在令人不放心。

非营利性组织 NumFOCUS 于2012年成立,它的诞生就是为了赞助科学计算领域的一些Python 包和项目。NumFOCUS 赞助的最受欢迎的项目包括pandas、NumPy、SciPy、Matplotlib和Project Jupyter。如今它也会对其他语言(比如R、Julia和JavaScript)的软件包提供支持。虽然还存在一些大型企业赞助商,但是每个人都可以作为一名自由社区成员加入NumFOCUS,另外捐献是可以减税的。

可以使用pip 安装任何功能的包,而对于Excel用户来说,最有趣的当然还是用于科学计算的包。下一节会介绍如何通过Python 进行科学计算。

1.2.3 科学计算

Python 成功的关键原因在于,它是作为一门通用编程语言诞生的。它的科学计算能力是在诞生之后通过第三方包的形式增加的。数据科学家可以和Web 开发者使用同一门语言来做实验和研究,最终 Web 开发者可以围绕数据科学家开发的计算核心开发一个随时可上线的应用程序,这正是Python的独特优势。使用一门语言来构建科研应用程序可以减少冲突、减少实现时间,甚至减少花费。诸如 NumPy、SciPy和pandas 之类的科学计算库给我们提供了一种简洁的方式来表达数学问题。作为一个例子,来看看现代投资组合理论中比较有名的投资组合方差公式。

为投资组合方差,w 为单个资产的权重向量,C 为投资组合方差矩阵。若 wC为Excel中的范围,则在VBA中可以像下面这样计算投资组合方差:

variance = Application.MMult(Application.MMult(Application.Transpose(w), C), w)

假定 wC 是pandas的DataFrame和NumPy中的数组,相比之下 Python 代码完全就像是数学记法:

variance = w.T @ C @ w

但这并非仅仅是美观和可读性方面的优势。NumPy和pandas 背后使用了预编译的Fortran 代码和C 代码,在处理大型矩阵时和VBA 相比有巨大的性能提升。

缺少对科学计算的支持是VBA 明显的短板,但即便是核心语言特性方面,它也显然不敌 Python。在下一节中你会看到这种差距。

1.2.4 现代语言特性

自Excel97 以来VBA在语言特性方面几乎没有任何重大改进,但这并不意味着 VBA 不再受到支持。为了让VBA 能够自动化Excel中的新功能,在每次 Excel发布新版本时,微软也会对VBA 进行更新,比如在Excel2016中就添加了自动化Power Query的支持。作为一门近 20年没有重大改进的语言,VBA 缺少了一些所有主流语言都有的现代语言概念。举例来说,VBA中的错误处理看起来就有些过时了。如果想在VBA中得当地处理错误,就要这样做:

Sub PrintReciprocal(number As Variant)
' There will be an error if the number is 0 or a string
On Error GoTo ErrorHandler
result = 1 / number
On Error GoTo 0
Debug.Print "There was no error!"
Finally:
' Runs whether or not an error occurs
If result = "" Then
result = "N/A"
End If
Debug.Print "The reciprocal is: " & result
Exit Sub
ErrorHandler:
' Runs only in case of an error
Debug.Print "There was an error: " & Err.Description
Resume Finally
End Sub

VBA的错误处理需要用到像 FinallyErrorHandler这样的标签,通过GoToResume语句可以让代码跳转到这些标签。在当时,人们认为标签是产生所谓意大利面式代码(对代码难以阅读和维护的一种打趣的说法)的罪魁祸首。这也就解释了为什么大多数还在积极开发中的语言引入了try/catch机制。这种机制在Python中叫作try/except,第11章会介绍。如果能熟练使用VBA,那么你可能也会喜欢 Python的类继承特性。这种面向对象编程特性正是VBA 所欠缺的。

除了一些现代语言特性,一门现代编程语言还有一项必备特性,那便是跨平台兼容性。下面来看看为什么跨平台兼容性如此重要。

1.2.5 跨平台兼容性

即便在一台运行着 Windows 或者 macOS的本地计算机上开发,在某个时候你也可能会想让代码在一台服务器或者云端上运行。服务器会通过其运算能力,让代码按计划执行,并使应用程序可以从任何地方访问。在第2章中我会介绍Jupyter 笔记本,展示如何在服务器上执行Python 代码。Linux 是一种非常稳定、安全且高效的操作系统,绝大多数服务器使用的是Linux。Python 程序可以在不修改代码的情况下在所有操作系统中运行,你可以轻松地从本地开发机器过渡到生产环境中。

相比之下,即便 ExcelVBA 可以在Windows和macOS中运行,但还是很容易写出一些只能在Windows中执行的代码。在VBA的官方文档或论坛中,经常会看到这样的代码:

Set fso = CreateObject("Scripting.FileSystemObject")

只要调用了CreateObject,或者被要求在VBA 编辑器的“工具 > 引用”选项中添加引用,你很有可能就是在处理只能在Windows 系统中运行的代码。如果想让Excel文件可在Windows和macOS中使用,则还需要重点关注是否使用了ActiveX 控件。ActiveX 控件就是那些可以放在表格上的按钮、下拉菜单之类的控件元素,这些控件只能在Windows中使用。如果想让工作簿也能在macOS中使用,那么务必避免使用这些控件!