Python与C的交互(一)
编者使用的是手机编辑此文档,因为一些未知原因,一次不能编写太久,否则无法发表。因此本系列文章目前还未完成,但将持续更新。
编者由于临近期末考试,还要背各科资料,因此version 7的更新将于7月2日前发布,请见谅。
(虽然以编者的水平写出来的文章,估计也没几个人看,不过还是要认真对待)本文更新日志:
2021.06.11 10:20 发布
2021.06.11 11:37 更新 version 2
2021.06.12 10:52 更新 version 3
2021.06.12 11:26 更新 version 4
2021.06.13 16:19 更新 version 5
2021.06.19 23:24 更新 version 6预计将在2021.07.02日前发布下一更新。
(1st) | CPython扩展(一)
CPython API
首先要下载CPython(注意不是Cython)的源码:Python官网。
下载好之后,可以看到里面有很多文件夹。我们要用的API在Include/Python.h
里面。
启动Python虚拟机并配置搜索路径
先上一段代码:
#include "cpython/Include/Python.h"
#include <stdio.h>
int main(int argc, char** argv)
{
Py_Initialize();
if (Py_isInitialized() == 0){
printf("Python虚拟机启动失败");
return 1;
} else {
printf("Python虚拟机启动成功");
}
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('C:/Users/Administrator/AppData/Local/Programs/Python/Python37-32/Libs/')");
// 这个路径是Python模块的放置路径
// TODO: 对Python的操作
// ...
Py_Finalize(); // 关闭虚拟机
return 0;
}
下面我们来看一下这段代码。
代码中出现的新内容
函数void Py_Initialize()
: 用于启动Python虚拟机
函数bool Py_isInitialized()
: 用于判断Python虚拟机是否成功启动。成功则返回true
,否则返回false
。
函数PyRun_SimpleString(const char* c)
: 用于执行单行Python语句。(这个函数的原形记不得了,抱歉)
函数void Py_Finalize()
: 用于关闭Python虚拟机。
这段代码首先调用了Py_Initialize
函数启动虚拟机,然后通过if
语句和Py_isInitialized
函数确认虚拟机是否开启成功,如果开启失败则退出程序。然后通过Python的sys
模块,执行Python代码,将Python模块路径加入到Python的模块搜索路径列表sys.path
中去。然后关闭虚拟机。
PyObject
结构体指针
在object.h
头文件中,定义了一个结构体PyObject
,所有Python对象都可以用PyObject结构体来表示。
请看如下C代码:
#include "cpython/Include/Python.h"
int main(int argc, char** argv)
{
Py_Initialize();
if (Py_isInitialized() == 0){return 1;}
PyObject* integer = PyInt_FromLong(4028);
Py_Finalize();
return 0;
}
代码中出现的新内容
结构体 PyObject: 包含了Python对象的引用计数(用于垃圾回收)、类型等。
函数PyObject* PyInt_FromLong(long arg1)
: 通过long
类型创建一个带int
类型的PyObject
结构体指针。
其实这段代码并没有什么实际用处,只是作为了解PyObject
这一节的代码片段而已。(这是编者的失误)
编写简单的Python扩展
Python扩展最后会编译为.so
文件或.pyd
文件(实质上就是动态链接库,在Windows下把.pyd
重命名为.dll
也可以给其它程序调用)。在Python中,.so、.pyd、.dll都可以作为Python模块,直接使用import
关键字或__import__
函数导入。1
请看这段C代码:
#include "cpython/Include/Python.h"
#include "stdio.h"
int main(int argc, char** argv)
{
Py_Initialize();
if (Py_isInitialized() == 0){return 1;}
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('C:/.../Python37-32/Lib')");
PyObject* mod = PyImport_ImportModule("os");
PyObject* dict = PyDict_GetDictFromModule(mod);
PyObject* getcwd = PyDict_GetItem(dict, "getcwd");
PyObject* args = PyTuple_New(0);
PyObject* ret = PyObject_CallObject(getcwd, args);
const char* cwd = PyStr_AsString(ret);
printf(cwd);
Py_Finalize();
}
代码中新出现的内容
函数PyObject* PyImport_ImportModule(const char* mod)
: 接受一个字符串参数(模块名),返回模块的PyObject*
对象。
函数PyObject* PyDict_GetDictFromModule(PyObject* mod)
: **(不知道现在是否已经弃用)**获取模块中的Dict(可理解为Python代码中的__dict__
)。
函数PyObject* PyDict_GetItem(PyObject* dict, const char* item)
: 从Python字典中获取对象。
函数PyObject* PyTuple_New(int l)
: 接受一个int
类型参数作为长度,返回一个元组(tuple)。这里tuple用来作为调用函数的参数集。
函数PyObject* PyObject_CallObject(PyObject* callable, PyObject* args)
: 调用一个Python对象。第一个参数是被调用对象,第二个参数是tuple(作为参数集)。
函数const char* PyStr_AsString(PyObject* string)
: 将一个str
类型的Python对象转化为const char*
类型字符串。
这段代码首先导入了os
模块,然后用dict
作为“中介”,获取了可调用对象getcwd
函数,再调用这个函数获取当前运行路径,最后将其转化为const char*
类型输出。
(2nd) | C调用Python
int
对象
[选修] int
的C原形2
int
在Python源代码的intobject.h
头文件中定义为一个结构体,原形如下:
// intobject.h
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
在这里面,PyObject_HEAD
宏指明了几乎每个内置对象都具有的内容,包括引用计数等;ob_ival
是长整数型,代表这个PyIntObject
对象所维护的实际数值。
我们知道,Python对象的元信息都保存在其对应的类型中。下面来看一看PyInt_Type
。
// intobject.c
PyTypeObject PyInt_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0,
"int",
sizeof(PyIntObject),
0,
(destructor) int_dealloc, /* tp_dealloc */
(printfunc) int_print, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
(cmpfunc) int_compare, /* tp_compare */
(reprfunc) int_repr, /* tp_repr */
&int_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
(hashfunc) int_hash, /* tp_hash */
0, /* tp_call */
(reprfunc) int_repr, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_BASETYPE, /* tp_flags */
int_doc, /* tp_doc */
0,
0,
0,
0,
0,
0,
int_methods, /* tp_methods */
0,
0,
0,
0,
0,
0,
0,
0,
int_new, /* tp_new */
(freefunc) int_free, /* tp_free */
};
基于上述二者,我们就可以执行一些算术操作。例如比较两个int
对象:
// intobject.c
static int int_compare(PyObject *v, PyObject *w)
{
register long i = v->ival;
register long j = w->ival;
return (i < j) ? -1 : (i > j) ? 1 : 0;
}
这就是int
的C原形了。
int
类型相关函数
Updating …
- 〔
.dll
〕在Python 2.x中可以作为模块导入,但在Python 3.x中废弃,只能重命名为.pyd
再导入。 ↩︎ - 〔
int
的C原形〕本小节的CPython源码摘自《Python源码剖析》,陈儒著,电子工业出版社出版。在此表示衷心的感谢。 ↩︎