文档章节

探究:使用C语言扩展Python3

SztWejtsNppTDCcfZYEe
 SztWejtsNppTDCcfZYEe
发布于 2014/07/16 15:40
字数 1560
阅读 202
收藏 1

背景

        用Python开发程序速度快,并且有许多现成模块可供使用,但执行速度相对较慢;C语言则正好相反,其执行速度快,但开发效率低。为了充分利用两种语言各自的优点,比较好的做法是用Python开发整个软件框架,而用C语言实现其关键模块。Python目前的主流版本是3,本文探究如何使用C语言扩展Python3。

系统环境

    Ubuntu 14.04 LTS x64 + Python 3.4.0 + gcc 4.8.2

C该怎么写?

    0、新建一个C文件

    话不多说,先新建一个C文件,命名 demo.c。

    1、包含必要的头文件

    所有的PythonAPI必须包含 Python.h,这个文件包含了<stdio.h><string.h><errno.h><stdlib.h>

    2、开始写功能函数

    1) 函数

    现在就可以开始写你想用C执行的函数了。这个函数的格式是有要求的,一个简单的函数如下所示:

static PyObject *
demo_hello(PyObject *self, PyObject *args) {
    PyObject *name, *result;
    if (!PyArg_ParseTuple(args, "U", &name))
        return NULL;
    result = PyUnicode_FromFormat("Hello, %S!", name);
    return result;
}

    我们来慢慢看这个函数。

    首先函数类型必须是 static PyObject *,函数名可以随便取,函数接收的参数必须是 (PyObject *self, PyObject *args)。

    所有从python传过来的参数都在 args 里,但是C和Python的数据类型不同,为了使C可以顺利的使用参数,我们需要在这之间做转化,这里使用了 PyArg_ParseTuple 函数(点击进入官方文档),其中的第二个参数是匹配字符串,函数根据这个匹配字符串将args的值传给后面的若干的参数。该例中“U”代表字符串,name的类型实际上可以定义为 char *。

    这个例子实际上没有使用任何C语言的数据类型,实际上PyArg_ParseTuple 函数提取出来的就是C的数据类型。

char *name;
int age;
if (!PyArg_ParseTuple(args, "si", &name, &age)) 
    return NULL;

    上面这个例子,python函数的参数是一个字符串,一个整型。通过PyArg_ParseTuple 函数转换成了C语言的 char* 和 int。

    按C语言的数据类型处理完数据,就要返回了,显然直接返回也是不行的。我们使用 Py_BuildValue 函数将C语言的数据转换成Python的数据,这实际上就是上文PyArg_ParseTuple的逆过程。

char total[10000];
memset(total, 0, sizeof(total));
strcat(total, "strcat() 函数用来连接字符串:");
strcat(total, "tset");
PyObject *result = Py_BuildValue("s", total);
return result;

    上面的代码在Python中调用之后收到的返回值就是 “strcat() 函数用来连接字符串:tset”。

    数据转换还有几个其他的函数,匹配字符串也有很多种写法。更多详细的请看官方文档。

    developerworks上介绍这个也有一篇不错的文章:http://www.ibm.com/developerworks/cn/linux/l-pythc/

    2) 模块结构

    写好了方法之后,我们需要构建一个方法表,如下所示

// method table
static PyMethodDef DemoMethods[] = {
    {"hello", // python method name
     demo_hello, // matched c function name
     METH_VARARGS, /* a flag telling the interpreter the calling 
                                 convention to be used for the C function. */
     "I guess here is description." },

     {NULL, NULL, 0, NULL}        /* Sentinel */
};

    每个方法需要关注的变量是浅两个,第一个变量是你在python中调用的名称,第二个变量是你的C语言函数名称,这两个名称会通过这个表匹配。第三个设置你函数传入参数的情况,第四个只是个描述,填 NULL 也行。

    DemoMethods中可以包含多个方法,每个方法按照这个依样画葫芦就成。这个数组实际上就是个 Method Table,记录了你所有的方法匹配表。

    实际上我们提供给Python的是一个模块,上面只是构建了方法表,下面再将 DemoMethods 作为变量传入,构建模块结构,如下所示。

// The method table must be referenced in the module definition structure.
static struct PyModuleDef demomodule = {
    PyModuleDef_HEAD_INIT,
    "demo",   /* name of module */
    NULL, /* module documentation, may be NULL */
    -1,       /* size of per-interpreter state of the module,
                or -1 if the module keeps state in global variables. */
    DemoMethods
};

    3) 初始化

    上面定义的结构,必须在模块的初始化函数中传给解释器。初始化函数必须命名为 PyInit_name(),其中name是模块的名称,这应该是在模块文件中定义的唯一的非静态项目:

// The initialization function must be named PyInit_name()
PyMODINIT_FUNC
PyInit_demo(void)
{
    return PyModule_Create(&demomodule);
}

    至此,C语言模块代码已经编写完成。

C该怎么编译?

    代码写好就应该开始准备编译为动态链接库方便python调用了。这里可以参考文档《Building C and C++ Extensions with distutils》,方法很简单:新建文件setup.py,配置编译:

from distutils.core import setup, Extension

module1 = Extension('demo',
                    sources = ['demo.c'])

setup (name = 'Demo hello',
       version = '1.0',
       description = 'This is a demo package by Sink',
       ext_modules = [module1])

    打开终端:

python3 setup.py build

    在 build 目录下可以找到编译好的 so 文件。

Python怎么调用?

    想怎么用就怎么用。so文件和py文件放一起,下面是最简单的例子。

import demo
hi = demo.hello("Sink")
print(hi)

有趣的研究

    Python传入的参数是tuple该怎么匹配

    如果Python调用函数传入的参数是tuple,PyArg_ParseTuple函数的匹配字符串可以通过( )来加以区分。

import demo
print(demo.tuple("Sink", (2, "Miku")))

    C语言中PyArg_ParseTuple匹配字符串可以通过()来匹配tuple的元素。

static PyObject *
demo_tuple(PyObject *self, PyObject *args) {
    char *a, *b;
    int c;
    if (!PyArg_ParseTuple(args, "s(is)", &a, &c, &b)) 
        return NULL;
    char str[255];
    sprintf(str, "%d", c);
    strcat(str, a);
    strcat(str, b);
    PyObject *result = Py_BuildValue("s", str);
    return result;
}

    上面代码输出的结果是 “2SinkMiku”。这些功能的核心就是PyArg_ParseTuple函数中的匹配字符串,写好这个字符串可以处理很多复杂的数据结构。

源代码

    已上传至OSC:http://www.oschina.net/code/snippet_818656_37278

© 著作权归作者所有

共有 人打赏支持
SztWejtsNppTDCcfZYEe
粉丝 2
博文 3
码字总数 2969
作品 0
徐汇
程序员
私信 提问
C/C++ 和 Python混合编程

链接:https://www.zhihu.com/question/23003213/answer/56121859 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 #include int main(int argc, char *...

sirius_0
2018/04/21
0
0
飞跃式发展的后现代 Python 世界

飞跃式发展的后现代Python世界 如果现代Python有一个标志性特性,那么简单说来便是Python对自身定义的越来越模糊。在过去的几年的许多项目都极大拓展了Python,并重建了“Python”本身的意义...

renwofei423
2013/08/08
7.6K
29
为什么有这么多 Python?

Python是出类拔萃的 然而,这是一句非常模棱两可的话。这里的"Python"到底指的是什么? 是Python的抽象接口吗?是Python的通用实现CPython吗(不要把CPython跟Cython搞混了)?亦或者指的完全...

renwofei423
2013/09/27
23.6K
32
SylixOS Python扩展库开发

1 适用范围 本文档适用于希望使用基于SylixOS进行Python扩展库开发的用户。 2 SylixOS Python简介 Python是一门面向对象的解释型的脚本语言,Python具有丰富和强大的库。它常被昵称为胶水语言...

zhaotongch
2018/09/03
0
0
Python 常用静态代码检查工具简介

对于我这种习惯了 Java 这种编译型语言,在使用 Python 这种动态语言的时候,发现错误经常只能在执行的时候发现,总感觉有点不放心。 而且有一些错误由于隐藏的比较深,只有特定逻辑才会触发...

不正经程序员
2018/07/24
0
0

没有更多内容

加载失败,请刷新页面

加载更多

转--C++ operator关键字(重载操作符)

operator是C++的关键字,它和运算符一起使用,表示一个运算符函数,理解时应将operator=整体上视为一个函数名。 这是C++扩展运算符功能的方法,虽然样子古怪,但也可以理解: 一方面要使运算...

天王盖地虎626
27分钟前
0
0
工作流题目

1. 当 创建流程审批系统时,我们需要 具备 哪些 功能? 答:(1) 流程管理 (2) 流程发起 (3) 流程 审批 (4) 流程 查询

杨凯123
53分钟前
2
0
每个 JavaScript 工程师都应懂的33个概念

简介 这个项目是为了帮助开发者掌握 JavaScript 概念而创立的。它不是必备,但在未来学习(JavaScript)中,可以作为一篇指南。 本篇文章是参照 @leonardomso 创立,英文版项目地址在这里。 ...

前端小攻略
今天
1
0
使用keepalived实现nginx的高可用

概述 是这样子的,我想让家中所有的应用服务都从nginx中出去,让nginx处于访问的最边缘地带,为了让nginx可靠性加强,所以nginx就得实现高可用,分别是下面两台机器要做nginx的集群 10.10.10...

bboysoulcn
今天
3
0
Mysql索引机制B+Tree

1、问题引入 有一个用户表,为了查询的效率,需要基于id去构建索引。构建索引我们需要考虑两个方面的问题,1个是查询的效率,1个是索引数据的存储问题。该表的记录需要支持百万、千万、甚至上...

万山红遍
今天
48
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部