尝试在C++项目中嵌入Python脚本
Hello World
首先写一个在C++代码中调用Python脚本输出 Hello World 的代码吧。1
2
3
4
5
6
7
8
9
10
int
main()
{
Py_Initialize();
PyRun_SimpleString("print('hello world from python')");
Py_Finalize();
return 0;
}
1 | cmake_minimum_required(VERSION 3.17) |
遇到的问题
在编写这一个 Hello World 程序时,网上的教程大多数都是使用的 Python 2.7 版本或者是直接从官网下载安装的或者是通过各种包管理器安装的 Python 3.x,对于这些情况,我们很容易就能找到对应的 Python dynamic link library。对于 mac os 来说,通过 brew install python 安装的 Python 的话,Python 的 dylib 就存在于该路径下 `/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib` (注意替换上述路径中的版本号为使用的版本号,本文中使用的是使用 brew 安装的 3.8.3 版本的 Python)。
当前,我的 MBP 上一共存在有三种不同的 Python:
1. Mac os 系统自带的 Python 2.7,用于各种系统用途
2. brew package manager 安装的 Python 3.8,用作一些使用 brew 安装的包的依赖
3. 使用 pyenv 安装的 Anaconda-2020.02,以及一系列的 conda environments。
从现在起,假设当前使用的Python是来自于 Anaconda的某一个 environment - OpenAI-Gym 。
一开始写好了C++部分之后,就开始编写相应的CMake文件了。这时候就犯了难,Python 的 include path 很好找,直接去这个 environment 下面找到 include 文件夹就好了,这个文件夹的路径就是我们需要的 include path。而 Python 的 dylib 则让我犯了难。理论上来说 library 应该是放在类似名为 lib 的文件夹下的。然后就去找了路径 environment/lib 下面寻找有没有 Python 的 dylib。一开始的时候不知道是不是看错了还是怎么的,以为没有(后面发现其实是有的)。之后就发现,诶,在 lib 文件夹里还存在一个叫做 python3.6 的文件夹。进去之后用 ls | grep python 搜索了一下发现没有任何结果。这就让我纳闷了。这时突然想起之前看的 Python 官方的关于将Python脚本嵌入到C++程序的文档,在那个文档的最后[1]有提到过C++编译需要设置的一些参数。其中就提到了可以使用一个 Python 自带的shell脚本 pythonX.Y-config --ldflags 来获取链接的参数。我执行之后得到的结果是这样的:-L/Users/USERNAME/.pyenv/versions/anaconda3-2020.02/lib/python3.7/config-3.7m-darwin -lpython3.7m -ldl -framework CoreFoundation。虽然也不是很懂这些参数的意思,不过我猜测应该是在这个路径( /Users/USERNAME/.pyenv/versions/anaconda3-2020.02/lib/python3.7/config-3.7m-darwin )下面就能找到 Python 的 dylib了。可惜事情并没有这么简单。这个路径下面与 library 有关的就只有一个:libpython3.7m.a 。通过 file 命令可以知道,这个文件是一个静态库。
1 | $ file libpython3.7m.a |
然后我就傻乎乎地直接把这个静态库当作动态库(dylib)写进了 CMake文件里。然后 build 就输出了超长一串各种看不懂的错误。
。。。
中间也找了各种办法,但是都不管用。中途想过会不会是因为我当时使用的是 Anaconda 发行版而不是普通从官网下载或者从包管理器安装的 Python。于是就去测试了一下用 brew 安装的 Python 3.8.3。
1 | cmake_minimum_required(VERSION 3.17) |
于是,直接成功啦。
当然到这里故事并没有结束,归根结底我还是想用 Anaconda 的。所以还是要解决一开始的问题。从这里开始我知道了一开始看到的那个 libpython3.7m.a 文件其实是一个静态库。那么我就想会不会是 Anaconda 版本的 Python 没有带 Python 的动态库呢。网上找了一圈没什么收获。只能自己折腾啦。
这时我又回到了 libpython3.7m.a 这个静态库所在的文件夹,执行了 ls -lh 命令,有了惊奇的发现:
1 | $ pwd |
咦,这个静态库原来是个软链接。于是就去到了它链接的文件所在的文件夹。然后用 ls | grep python 搜索了一下 (其实应该用 find . -name '*python*' )。意外收获
1 | $ pwd |
libpython3.6m.dylib 不就是我一直苦苦寻找的动态链接库吗!
在CMake设置好了链接的目录以及链接的动态库的名字之后,尝试着build,结果又又又出问题了。输出如下:
1 | Could not find platform independent libraries <prefix> |
拿着 Fatal Python error: Py_Initialize: unable to load the file system codec 这段话上网查了一下,问题大概出在系统里有多个 Python 版本,结果没找到正确的那个。
解决方案很简单,设置一下环境变量 PYTHONHOME ,将其指向正确的Python目录即可。我这里就把它设置为了 Anaconda 的 OpenAI-Gym environment 的目录( /Users/USERNAME/.pyenv/versions/anaconda3-2020.02/envs/OpenAI-Gym )。然后,终于输出了正确的结果啦!
碎碎念,这一下就折腾了三个多小时,最后半个小时是在敲这个事件记录。
References:
1. Embedding Python in Another Application — Python 3.8.5 documentation
c++ - Py_Initialize fails - unable to load the file system codec - Stack Overflow