在上一节中,通过一个简单的例子介绍了C语言扩展Python内建模块的整体流程,从本节开始讲开始深入讨论一些细节问题,在细节讨论中从始至终都会涉及【引用计数】的问题。首先讨论C语言封装的Python函数的参数解析与函数结果返回的封装。

参数解析

最常用的接口是

int PyArg_ParseTuple(PyObject *arg, char *format, ...);

arg是一个tuple object,从python传递给C函数;format参数必须是一个字符串,通常每个字符代表一种类型;剩下的参数是与format相对应的各个变量的地址,返回值是一个整型,解析成功返回1,解析出错返回0.

函数接收的Python Object参数是borrowed reference,所以不需要增加引用计数。

Example:

int ok;
int i, j;
long k, l;
const char *s;
int size; ok = PyArg_ParseTuple(args, ""); /* 无参数 */
/* Python call: f() */
ok = PyArg_ParseTuple(args, "s", &s); /* 参数为一个字符串 */
/* Possible Python call: f('whoops!') */
ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* 参数为两个长整型与一个字符串 */
/* Possible Python call: f(1, 2, 'three') */
{
const char *file;
const char *mode = "r";
int bufsize = ;
ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
/* 参数至少有一个字符串,可以另外有一个字符串或整型 */
/* Possible Python calls:
f('spam')
f('spam', 'w')
f('spam', 'wb', 100000) */
}
{
int left, top, right, bottom, h, v;
ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
&left, &top, &right, &bottom, &h, &v);
/* 参数为两个元组,第一个元组有两个元组元素,每个元组为两个整数构成 */
/* Possible Python call:
f(((0, 0), (400, 300)), (10, 10)) */
}
PyObject* p;
ok = PyArg_ParseTuple(args, "O", &p); /* 参数为一个PyObject对象,可以表示Python中的任意类型 */
/*Possible Python call: f((1,2))*/

Python C API中提供的常见形式字符串如下所示(没有全部列出,其余的可以参考Python2.7文档):

s  ==> const char*, 将Python字符串转为字符指针;
s# ==> const cahr*, Py_ssize_t, 将Python字符串转为字符指针以及字符创长度;
b ==> unsigned char, Python非负整数转为C unsigned char;
B ==> unsigned char, Python整数转为C unsigned char;
h ==> short in
H ==> unsigned short int
i ==> int
I ==> unsigned int
l ==> long int
k ==> unsigned long
L ==> PY_LONG_LONG,将Python整数转为C的long long,有些平台可能不支持;
K ==> unsigned PY_LONG_LONG
f ==> float, 将Python的浮点数转为C的float。Pyhton中仅有double类型,所以这里会有精度丢失。
d ==> double, 将Python浮点数转为C的double,无精度丢失
O==> PyObject*, 将Python对象保存在PyObject*中,这里object的引用计数不会增加;
(items)==> tuple
/*其他字符*/
|
|后面的参数是可选的, PyArg_ParseTuple中需要提供缺省参数的默认值;
:
字符串参数列表在:结束,:后面的表示对函数的解释说明;

;后面的用于错误说明,替代默认的错误信息
static PyObject* distance(PyObject* self, PyObject* args)
{
double x0, y0, z0, x1, y1, z1; if (!PyArg_ParseTuple(args, "(ddd)(ddd)", &x0, &y0, &z0, &x1, &y1, &z1)) /*接受两个tuple类型的参数*/
{
return NULL;
}
return PyFloat_FromDouble(sqrt((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1) + (z0 - z1) * (z0 - z1)));
}

结果返回

与参数解析函数PyArg_ParseTuple相对应的是:

PyObject *Py_BuildValue(char *format, ...);

format同样指明了各个参数列表的各个参数的类型,但是传递给该函数的参数不能是指针,这里有PyArg_ParseTuple不同,只传递值即可。另外一个重要的区别是,Py_BuildValue返回的是PyObject*, 引用计数自动为1,如果传递给该函数一个PyObject*参数,比如以下代码,此时p的引用参数会increamented by one.

PyObject*  p = PyFloat_FromDouble(1.0);
Py_BuildValue('O', p);

在Python源码中: Py_BuildValue会调用static PyObject* do_mkvalue(const char **p_format, va_list *p_va, int flags),这里对于‘O'的处理如下:

        case 'N':
case 'S':
case 'O':
if (**p_format == '&') {
typedef PyObject *(*converter)(void *);
converter func = va_arg(*p_va, converter);
void *arg = va_arg(*p_va, void *);
++*p_format;
return (*func)(arg);
}
else {
PyObject *v;
v = va_arg(*p_va, PyObject *);
if (v != NULL) {
if (*(*p_format - ) != 'N')
Py_INCREF(v); /*如果format不是'N',那么引用计数会增加1,如果format是'N',引用计数不变*/
}
else if (!PyErr_Occurred())
/* If a NULL was passed
* because a call that should
* have constructed a value
* failed, that's OK, and we
* pass the error on; but if
* no error occurred it's not
* clear that the caller knew
* what she was doing. */
PyErr_SetString(PyExc_SystemError,
"NULL object passed to Py_BuildValue");
return v;
}

Example:

左边是函数调用形式,右边是返回的Python value:

Py_BuildValue("")                        None
Py_BuildValue("i", )
Py_BuildValue("iii", , , ) (, , )
Py_BuildValue("s", "hello") 'hello'
Py_BuildValue("ss", "hello", "world") ('hello', 'world')
Py_BuildValue("s#", "hello", ) 'hell'
Py_BuildValue("()") ()
Py_BuildValue("(i)", ) (,)
Py_BuildValue("(ii)", , ) (, )
Py_BuildValue("(i,i)", , ) (, )
Py_BuildValue("[i,i]", , ) [, ]
Py_BuildValue("{s:i,s:i}",
"abc", , "def", ) {'abc': , 'def': }
Py_BuildValue("((ii)(ii)) (ii)",
, , , , , ) (((, ), (, )), (, ))

除了使用Py_BuildValue函数返回Python对象之外,还可以调用每个类型所提供的封装函数,比如我们之前的test模块中,distance函数需要返回一个Python float对象,那么可以调用floatobject提供的PyFloat_FromDouble:

PyObject *
PyFloat_FromDouble(double fval)
{
register PyFloatObject *op;
if (free_list == NULL) {
if ((free_list = fill_free_list()) == NULL)
return NULL;
}
/* Inline PyObject_New */
op = free_list;
free_list = (PyFloatObject *)Py_TYPE(op);
(void)PyObject_INIT(op, &PyFloat_Type); /*初始化引用计数*/
op->ob_fval = fval;
return (PyObject *) op;
}

扩展Python模块系列(三)----参数解析与结果封装的更多相关文章

  1. 扩展Python模块系列(二)----一个简单的例子

    本节使用一个简单的例子引出Python C/C++ API的详细使用方法.针对的是CPython的解释器. 目标:创建一个Python内建模块test,提供一个功能函数distance, 计算空间中两 ...

  2. 扩展Python模块系列(一)----开发环境配置

    本系列将介绍如何用C/C++扩展Python模块,使用C语言编写Python模块,添加到Python中作为一个built-in模块.Python与C之间的交互目前有几种方案: 1. 原生的Python ...

  3. 扩展Python模块系列(五)----异常和错误处理

    在上一节中,讨论了在用C语言扩展Python模块时,应该如何处理无处不在的引用计数问题.重点关注的是在实现一个C Python的函数时,对于一个PyObject对象,何时调用Py_INCREF和Py_ ...

  4. 扩展Python模块系列(四)----引用计数问题的处理

    承接上文,发现在使用Python C/C++ API扩展Python模块时,总要在各种各样的地方考虑到引用计数问题,稍不留神可能会导致扩展的模块存在内存泄漏.引用计数问题是C语言扩展Python模块最 ...

  5. python之命令行参数解析模块argparse

    """argparse模块使得写用户友好性命令行接口很容易,程序定义所需要的参数,argparse会从ays.argv中提取出这些参数.argparse模块也能自动的产生 ...

  6. 嵌入Python系列 | 调用Python模块中无参数函数

    开发环境 Python版本:3.6.4 (32-bit) 编辑器:Visual Studio Code C++环境:Visual Studio 2013 需求说明 在用VS2013编写的Win32程序 ...

  7. C语言扩展Python模块

    1. 先创建一个PythonDemo.cpp文件: //c/c++中调用python脚本,配置步骤参见上一篇:C/C++与python交互 \  C/C++中调用python文件. #include ...

  8. Python学习笔记之参数解析

    python提供了两种方法进行命令行的参数解析,分别是getopt和optparse类中的模块OptionParser,下面分别详细了解这两个模块: 1.getopt模块 首先复习C语言的命令行解析: ...

  9. Python 中命令行参数解析工具 docopt 安装和应用

    什么是 docopt? 1.docopt 是一种 Python 编写的命令行执行脚本的交互语言. 它是一种语言! 它是一种语言! 它是一种语言! 2.使用这种语言可以在自己的脚本中,添加一些规则限制. ...

随机推荐

  1. 【Android Developers Training】 65. 应用投影和相机视图

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  2. Kotlin入门第四课:简单工厂模式

    Kotlin基础知识的学习,请参考之前的文章: Kotlin入门第一课:从对比Java开始 Kotlin入门第二课:集合操作 Kotlin入门第三课:数据类型 初次尝试用Kotlin实现Android ...

  3. WPF 杂谈——Trigger触发器

    笔者在使用的WPF过程中,见过的触发器有三种:Trigger.DataTrigger.EventTrigger.其中最为常用的要属Trigger.至于触发器的作用就是当某个属性的值发生变化,应该去做某 ...

  4. 14.什么是jsp动作

    JSP动作元素(action elements),动作元素为请求处理阶段提供信息.动作元素遵循XML元素的语法,有一个包含元素名的开始标签,可以有属性,可选的内容,与开始标签匹配的结束标签. 包含的类 ...

  5. sql将一列数据拼成一个字符串的方法

    SELECT STUFF(CONVERT(VARCHAR(500), ( SELECT TOP 10 ',' + BG_Country FROM dbo.BS_Budget FOR XML PATH( ...

  6. 【转】SQL多条件模糊查询解决方案-存储过程

    前言:   算法的基本特性在前几篇博客中已经做了详细的说明,经过不断的改进优化,到归仓的时候了,也就是说,该算法告一段落,不再更新. 作为最终的解决方案,简要的总结一下算法特性,以方便读者参阅. l ...

  7. 关于引入多个jquery冲突的问题(附一个很好用的validate前端验证框架及使用方法)

    废话不多说,进入正题: 如果一个jsp中想要使用两个不同版本的jquery怎么办呢?客官往下看: <script src="${ctxStatic}/jquery/jquery-1.8 ...

  8. Oracle11g 创建表空间、创建用户、角色授权、导入导出表以及中文字符乱码问题

    前提:本机已经安装了Oracle11g数据库. 需求:使用PL SQL数据库连接工具操作Oracle数据库 一.创建表空间和用户      想要操作数据库,首先需要创建用户并给用户授予权限:在创建用户 ...

  9. Django 学习笔记(二)

    Django 第一个 Hello World 项目 经过上一篇的安装,我们已经拥有了Django 框架 1.选择项目默认存放的地址 默认地址是C:\Users\Lee,也就是进入cmd控制台的地址,创 ...

  10. JavaScript一个whenReady函数,监听及注册事件

    /** * 传递函数给whenReady(),当文档解析完成且为操作准备就绪时, * 函数将作为文档对象的方法调用 * DOMContentLoaded.readystatechange或load事件 ...