在应用程序中嵌入Python¶
在应用程序中嵌入Python或其它什么,关键就是如何让应用程序与Python脚本交互,即在 应用程序中解释执行Python代码。回想一下你是如何调用Python标准模块的?应用程序采 取相同的方式与Python交互:
- 导入Python模块(加载脚本)
- 获取模块中的函数/类/类的方法等
- 构建参数,调用Python函数;访问属性等
- 解析返回值
在应用程序中嵌入Python或其它什么,关键就是如何让应用程序与Python脚本交互,即在应用程序中解释执行Python代码。回想一下你是如何调用Python标准模块的?应用程序采取相同的方式与Python交互:
- 导入Python模块(加载脚本)
- 获取模块中的函数/类/类的方法等
- 构建参数,调用Python函数;访问属性等
- 解析返回值
一个简单的例子¶
C程序:
// file: main.c
#include <Python.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define CHECK_NULL_ASSERT(p) \
if (NULL == (p)) {\
PyErr_Print();\
assert(0);\
}
int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: exe <python_source_file> <func_name>");
}
setenv("PYTHONPATH", "./", 1);
Py_SetProgramName("main");
Py_Initialize();
PyObject* pModule = PyImport_ImportModule(argv[1]);
CHECK_NULL_ASSERT(pModule);
PyObject* pDict = PyModule_GetDict(pModule);
CHECK_NULL_ASSERT(pDict);
PyObject* pFunc = PyDict_GetItemString(pDict, argv[2]);
CHECK_NULL_ASSERT(pFunc);
if (PyCallable_Check(pFunc)) {
PyObject_CallObject(pFunc, NULL);
}
else
{
PyErr_Print();
}
Py_DECREF(pModule);
Py_Finalize();
return 0;
}
编译:gcc main.c -o main `pkg-config –libs –cflags python2`
python文件:
# file: example.py
def echo():
print("Hello World!")
执行:./main example echo。打印:”hello world!”
上面C程序中的基本流程就是按照前面提到的步骤进行的。
Python库搜索路径¶
应用程序加载python脚本模块,有一个模块的搜索路径问题,python库的默认搜索路径与Python的安装路径有关,可以通过下面的代码查看:
import sys
print sys.path
另外可以通过两个环境变量:PYTHONHOME和PYTHONPATH来调整。根据官方文 档中的说明:
- PYTHONHOME 用于更改Python标准库的(搜索)位置,默认位置为:prefix/lib/pythonversion和exec_prefix/lib/pythonversion。如果将PYTHONHOME设置为单个目录,则prefix和exec_prefix将取相同值。也可以设置为不同的值:prefix:exec_prefix
- PYTHONPATH 用于增加模块文件的默认搜索路径。默认搜索路径与安装相关,通 过为:prefox/lib/pythonversion,且它通常应该添加到变量PYTHONPATH中。
调整库搜索路径¶
调整python解释器的搜索路径,其实就是修改PYTHONHOME和PYTHONPATH两个环境变量。方法有:
使用setenv来修改PYTHONHOME和PYTHONPATH。如上面的代码
setenv("PYTHONPATH", "./", 1);
在命令行修改环境变量
Python API函数PySys_SetPath(char*)
// ...... Py_Initialize(); PySys_SetPath("./"); // ...... Py_Finalize();
加载Python模块¶
加载Python模块(脚本文件)
PyObject* PyImport_ImportModule (const char *name) 相当于:import name。name可以是内置模块名,也可以是开发的新模模块名(文件名)
PyObject* PyImport_Import(PyObject *name) 参数name应该是一个PyObject对象,如:
PyObject* pFileName = PyString_FromString(argv[1]); PyObject* pModule = PyImport_Import(pFileName);
取得函数的引用¶
如果要执行脚本中的某个特定的函数,首先要取得这个函数:
int PyObject_HasAttrString(PyObject* o, const char* attr_name); PyObject* PyObject_GetAttrString(PyObject* o, const char* attr_name); int PyObject_SetAttrString(PyObject* o, const char* attr_name, PyObject* v); int PyObject_DelAttrString(PyObject* o, const char* attr_name);
相当于: obj.name [= val]
通过这种方法不仅是取得属性,也可以是函数
构造参数调用函数¶
C调用Python函数,向Python函数传递参数,则需要将C的数据类型转换为Python的数据类型,然后再调用Python函数。
将C类型转换为Python类型¶
PyObject* Py_BuildValue(const char* format, ...); 可以将各种C数据类型,转换为python数据类型。
如:
Py_BuildValue("") None Py_BuildValue("i", 37) 37 Py_BuildValue("ids", 37, 3.4, "hello") (37, 3.4, "hello")元组 Py_BuildValue("[ii]", 37, 3) [37, 3] Py_BuildValue("{s:i,s:i}", "x", 3, "y", 2) {"x":1, "y":2}
int PyArg_ParseTuple(PyObject *args, const char *format, ...)
int PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char *keywords[], ...)
int PyArg_Parse(PyObject *args, const char *format, ...)
调用python中的函数¶
- PyCallable_Check(PyObject*) 检查对象是否可以被调用执行
- PyObject_CallObject(PyObject* callable_obj, PyObject* arg) 传递参数arg调用对象callable_obj。等同于python中: apply(callable_object, args)。注意args是元组。
- PyObject* PyObject_CallFunction(PyObject* callable, char* format, ...) 直接以C类型数据调用callable对象, 格式串format与Py_BuildValue一样。
- PyObject* PyObject_CallFunctionObjArgs(PyObject* callable, ..., NULL)
- PyObject* PyObject_CallMethod(PyObject* o, char* method, char* format, ...) 等同于:o.method(args)
- PyObject* PyObject_CallMethodObjArgs(PyObject* o, PyObject* name, ..., NULL)
处理返回值¶
处理返回值,即将Python的数据类型转换为C的类型
- long PyInt_AsLong(PyObject*);
- long PyLong_AsLong(PyObject*);
- double PyFloat_AsDouble(PyObject*)
- char* PyString_AsString(PyObject*)
扩展嵌入的Python解释器¶
通过Python C API可以将标准的Python解释器嵌入到应用程序中,但是如何扩展这个解释 器呢?即在解释器中内置一些模块。
首先看一下,Python文档中的例子:
#include <Python.h>
static int numargs=0;
/* Return the number of arguments of the application command line */
static PyObject*
emb_numargs(PyObject *self, PyObject *args)
{
if(!PyArg_ParseTuple(args, ":numargs"))
return NULL;
return Py_BuildValue("i", numargs);
}
static PyMethodDef EmbMethods[] = {
{"numargs", emb_numargs, METH_VARARGS,
"Return the number of arguments received by the process."},
{NULL, NULL, 0, NULL}
};
int
main(int argc, char *argv[])
{
Py_SetProgramName(argv[0]); /* optional but recommended */
Py_Initialize();
numargs = argc;
// 关键之处
Py_InitModule("emb", EmbMethods);
PyObject* pModule = PyImport_ImportModule(argv[1]);
PyObject* pFunc = PyObject_GetAttrString(pModule, argv[2]);
if (PyCallable_Check(pFunc)) {
PyObject_CallObject(pFunc, NULL);
}
Py_Finalize();
return 0;
}
上面的代码中,内置的Python解释中扩展了一个模块”emb“,即应用程序提供了一个API可供脚本使用,在下面的python脚本中导入模块,并执行其中的功能。python脚本为:
import emb
def echo():
print(print "Number of arguments", emb.numargs())
这样就实现了Python/应用程序的双向通讯。
再看看上面的代码,相当复杂麻烦,开发人员要专门写每一个API,并规划导出,而用C/C++来扩展Python有很多更方便的工具,如swig, Boost.Python等。
如果将用这些工具导出的API扩展到嵌入在程序内的解释器呢?
swig与扩展嵌入的python解释器¶
我们建立一个小工程:
foo.h
#ifndef __FOO_H__ #define __FOO_H__
int fib(int n); #endif
foo.c
#include "foo.h" int fib(int n) { if (n <= 1) { return 1; } else { return n * fib(n - 1); } }
foo.i
%module fool %{ #include "foo.h" %} %include "foo.h"
test.py
import foo def calc(n): return foo.fib(n)
main.c
#include <Python.h> #include <stdio.h> #include <stdlib.h> // 由swig生成 extern void init_foo(); int main(int argc, char* argv[]) { if (argc < 2) { fprintf(stderr, "Usage:\n\t%s <n>\n", argv[0]); exit(1); } setenv("PYTHONPATH", "./", 1); Py_Initialize(); // 关键代码,由swig生成,在文件foo_wrap.c中 init_foo(); PyObject* pModule = PyImport_ImportModule("test"); PyObject* pFunc = PyObject_GetAttrString(pModule, "calc"); PyObject* pValue = Py_BuildValue("i", atoi(argv[1])); if (PyCallable_Check(pFunc)) { PyObject* pReturn = PyObject_CallFunctionObjArgs(pFunc, pValue, NULL); if (NULL == pReturn) { PyErr_Print(); } fprintf(stdout, "%ld\n", PyInt_AsLong(pReturn)); Py_DECREF(pReturn); } Py_DECREF(pValue); Py_DECREF(pFunc); Py_DECREF(pModule); Py_Finalize(); return 0; }
Makefile
CFLAGS = -Wall -g `pkg-config --libs --cflags python2` all: main main: main.c foo.c foo_wrap.c foo_wrap.c: foo.h foo.i swig -python foo.i clean: rm -f main foo_wrap.c foo.py \*.pyc
运行make之前,./main 5
参数资料¶
- Python Essential Reference(4th)
- Python Document