编译你的 Python 代码

提到 Python,或许你会想到,这是一门解释型的语言,确实是,但是也不是。首先区分一点,从形式上来说,Python 是一门语言,但是他有很多种实现(implementation),你可以参考这份清单,其中最著名的当然是 CPython ,这门语言的参考实现,他是解释执行代码的。但是理论上,他也可以有编译型的实现,这就是我今天要介绍的,他叫 Nuitka

Nuitka Logo

安装

Nuitka 的安装方式非常简单,他是一个普通的 Python Package,你可以从 pypi.org 下载。

1
2
3
pip install nuitka

python -m nuitka --help

不过,要正确编译你的代码,还需要一些额外的软件:

  • 一个 C 语言的编译器,gcc 或者 clang
  • CPython,2.6,2.7 或者 3.3 以上。

官方还没有支持 macOS

接下来我会用 docker 来演示,使用 python:3.7 镜像

1
docker run --rm python:3.7 bash

使用

我准备了这样一个程序,他用来计算斐波那契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# app.py
import sys
import time

from fab import fab


if __name__ == '__main__':
try:
n = int(sys.argv[1])
except IndexError:
n = 30

start_time = time.time()
result = fab(n)
end_time = time.time()
print(f'fab({n}) = {result}')
print(f'take {end_time - start_time:.3f} seconds')
1
2
3
4
5
6
7
8
# fab.py
def fab(n):
if n < 2:
return n
return fab(n - 1) + fab(n - 2)

if __name__ == '__main__':
print(f'fab(30) = {fab(30)}')

先试一下用 CPython 执行

1
2
3
python app.py
# fab(30) = 832040
# take 0.429 seconds

编译整个程序

如果你想编译整个程序,执行下面这条指令

1
python -m nuitka --portable app.py

这行命令执行完毕,我们会得到一个文件夹叫做 app.dist 它包含了所有需要的东西,你可以把它拷贝到另一台机器上执行,即使那台机器上没有安装 Python

1
2
3
4
cd app.dist
./app.exe
# fab(30) = 832040
# take 0.245 seconds

编译一个模块/包

Nuitka 也可以编译一个模块

1
2
python -m nuitka --module fab.py
# produce fab.so

你可以在 CPython 中导入编译后的模块

1
2
3
4
5
6
>>> from fab import fab
>>> fab
<compiled_function fab at 0x7efe9fa0f3c8>
>>> fab(30)
832040
>>>

Why

兼容性。Nuitka 和其他类似的项目的目标不太一样,他更关注与 CPython 之间的兼容性,Nuitka 的测试用例就来自 CPython。只要你的代码可以在 CPython 下面执行,用 Nuitka 编译出来的代码也应该正常执行。

便于分发。如果你打算用 Python 开发一个客户端应用,那么你要如何交付给你的用户呢?告诉他们,先去 python.org 下载安装 Python XXX 版本,然后下载你的程序,然后执行 pip install…? 这肯定不是一个好主意。如果可以的话当然是只需要下载程序,然后双击执行。Nuitka 提供了这样的一个可能性。

加速。因为是编译成机器代码,相比解释执行,减少了一些不必要的 overhead,比如说解析源代码,生成 bytecode。另外,因为编译成机器码的缘故,Nuitka 还可以做一些优化,作者声称一般能增加两倍的执行速度,所有需要做的只是切换到 Nuitka。

混淆源代码。或许你要分发给你的用户,但是有不想把源代码给他们。

以上

Handling Python enviroments

python2 将在 2020 年结束支持,许多项目已经开始迁移或者计划迁移到 python3 上面。IPython 已经在新版本中取消 python2 支持;Django 2.0 取消 python2 支持,numpy 即将结束 python2 支持。

大家终于开始接受 python3 了,不过在这个时期内,处理 python 版本问题变的愈加痛苦。最近 macOS 上流行的包管理工具 Homebrew 做了非常激进的一步,它们把 python 链接到 python3 了,这甚至违反了 PEP394 而招致一部分人不满。当我执行 brew upgrade 之后,一些 python 写的工具没法用了。

所以不得重新整理这些 python 版本的管理工具、策略,很多曾经写下的配置现在已经不知道时什么作用了。趁这个机会重新梳理一遍。

注意,我使用的操作系统是 macOS,shell 用的是 fish,下面这些设置可能并不适合其他组合。一些工具的使用方式这里不一一列出,如果你感兴趣,可以点击链接查看详细的使用方法。

命令行工具

一些命令行工具是用 python 写的,比如说 fabric、ansible、pipenv。这些工具一般来说应该是安装在系统的 python 上面,pip 会自动链接命令到比如说 /usr/local/bin 下面,这样你就可以直接在终端里面输入 fab 而不是 /path/to/python -m fabric

不过我不想把系统的 python 搞的乱七八糟的,而且这非常不方便管理,比如说你要更新某个工具的时候,或者你要卸载某个工具的时候,因为不同的工具可能只能运行在某个版本的 python 上,当你要更新、卸载它们的时候,首先你需要找到它们安装在哪个解释器上。

所以,我使用 pipsi 来管理这些命令行工具。

相比 Homebrew 上的 python,操作系统自带的 python 更新比较没有那么频繁,所以稍微修改了一点安装方法,能够减少因为更新 python 导致的问题。

1
2
3
curl -sSL https://bootstrap.pypa.io/get-pip.py | sudo -H /usr/bin/python
sudo /usr/bin/python -m pip install virtualenv
curl https://raw.githubusercontent.com/mitsuhiko/pipsi/master/get-pipsi.py | sudo /usr/bin/python

这样安装 python 的命令行工具:

1
pipsi install fabric

如果需要指定解释器版本:

1
pipsi install --python python3 jupyterlab

更新、删除某个工具变的非常方便

1
2
3
pipsi upgrade fabric
# or
pipsi uninstall fabric

项目执行环境

virtualenv

最常用就是 virtualenv 了,这其实也是一个命令行工具,不过他过于基础, 甚至一些环境管理工具也依赖它,所以比较适合安装在系统级别,在上一节,更具体的,应该把它和这些管理工具安装在同一个 python 解释器上。virtualenv 已经安装在系统的 python 上面了,所以不需要再安装一次,直接用就可以了。

我一般会把运行环境放在项目的目录下面,这样,在文本编辑器上,可以在项目目录上看到运行环境的目录。一些文本编辑器会索引项目的文件,比如说 Sublime Text ,你可以使用 cmmand + P 非常快速的定位到你想查看的文件。

virtualenvwrapper (virtualfish)

这个工具对我来说,最主要的目是,当我要试用某个第三方包的时候,我希望可以快速的创建一个执行环境,尝试一下,完事之后直接删除这个环境,确保操作系统的整洁。

virtualenvwrapper 包含了这样一个命令 mktmpenv 它会自动创建一个临时文件夹,设置好 python 的环境,就像 virtualenv 已将,你可以直接指定 virtualenv 的参数,比如说 --python 设置 python 的版本。额外的,他还会设置一些钩子,当你退出这个环境后 (deactivate),这个临时文件夹会自动删除。

不过 virtualenvwrapper 好像不支持 fish, 所以才有另外一个工具,virtualfish ,它做的事情基本上和 virtualenvwrapper 一样,而且还有一个插件,提供了 virtualenvwrapper 一样的命令。而且对于创建临时环境这件事上,做的比 virtualenvwrapper 还要好,不只是在 deactivate 后自动删除,你关闭终端的时候它也会自动删除。

因为 virtualfish 并没有设置命令行的 entrypoint 所以没办法用 pipsi 安装:

1
2
sudo /usr/bin/python -m pip install virtualfish
echo 'eval (/usr/bin/python -m virtualfish compat_aliases)` >> ~/.config/fish/config.fish

以上