Python/C API 3.4 简介

Python/C API 3.4 简介

英语水平太次,板砖轻拍!!! 原文 Python/C API 官方文档

1 头文件

Python/C API的函数、类型、宏等都定义在头文件Python.h中,所以在使用之前要include此头文件。

注意:因为Python会定义一些预处理宏,而在一些系统中这些宏会影响到标准头文件,所以要在include标准头文件之前include此头文件。

在Python.h中,所有用户可见的命名定义都以Py或者_Py为前缀,其中_Py开头的命名由Python内部使用,用户不能直接使用。

注意: 用户代码中千万不要定义以Py或_Py开头的名字,以为这会降低代码的可读性,并对以后Python版本的更新带来影响。

注意: 尽量将将Python.h的父目录加入INCLUDE搜索目录中,直接include Python.h。

尽管API全部使用C语言实现,但头文件已经在入口点声明了extern “C”,所以C++程序中可以直接使用,不用特殊操作。

2 对象、类型、引用计数

多数Python/C API函数都有一个甚至多个参数,返回值多为PyObject*型,PyObject*类型是一个指向不透明数据类型的指针,该不透明数据类型可以表示任意的Python对象,几乎所有的Python对象都存在heap中。千万不要声明PyObject型的auto或static变量,只允许声明PyObject*型的变量,唯一的特例是异常对象,它们是static PyTypeObject类型的对象,因为它们决不能被释放。

所有的Python对象(甚至是Python整数)都包含一个type和一个reference count,可使用PyList_Check(a)检查a是否为list类型,是则返回true。

使用Py_INCREF()为对象的引用计数加1,Py_DECREF()为对象的引用计数减1。

ownership of reference 中的ownership指的是引用而不是对象,对象不会被拥有,其只会被共享。ownership of reference 是可以转移的,接收ownership of reference 的代码负责该引用的decref过程(调用Py_DECREF() 或者Py_XDECREF() )。

当一个函数向其调用者传递ownership of reference 时,调用者就收到了一个新的reference;当没有ownership传递时,调用者会borrow该引用。对于后一种情况,调用者不用做额外的处理。

当函数A调用函数B并将一个对象的引用作为参数传递给B时,有两种可能:函数B偷走了该对象的引用,或者没有。Steal a reference 意味着函数B已经拥有了该引用,A也将不在对该引用负责。

很少有函数偷引用,已知的两个函数是PyList_SetItem()PyTuple_SetItem(), 这两个函数如此设计,是因为想list和tuple添加新建数据的习惯用法,例如以下代码会创建tuple(1, 2, “three”)

PyObject *t;
t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyLong_FromLong(1L));
PyTuple_SetItem(t, 1, PyLong_FronmLong(2L));
PyTuple_SetItem(t, 2, PyUnicode_FromString("three"));

对应地,可以使用PyList_New()PyList_SetItem()添加数据。
PyLong_FromLong()创建了一个引用后,该引用就立即被PyTuple_SetItem()偷走了。如果想在引用被偷走之后继续使用该对象,需要在被偷之前先调用Py_INCREF() 获得另一个引用即可。

注意PyTuple_SetItem() 是设置tuple成员的唯一途径,即使是PySequence_SetItem()PyObject_SetItem() 也不能为tuple添加成员,因为tuple是不可变数据类型。

在实际应用中,很少使用上面的方法创建和设置list和tuple,有一个通用函数Py_BuildValue(),该函数根据一个format string 从C变量构造一个对象,如下所示

PyObject *tuple, *list;
tuple = Py_BuildValue("(iis)", 1, 2, "three"); // 注意小括号
list  = Py_BuildValue("[iis]", 1, 2, "three"); // 注意中括号

更为普遍的做法是使用PyObject_SetItem()等等,这些函数不会偷走参数的引用,例子如下

int set_all(PyObject *target, PyObject *item)
{
    Py_ssize_t i, n;
    n = PyObject_Length(target);
    if (n < 0) return -1;
    for (i = 0; i < n; ++i)
    {
        PyObject *index = PyLong_FromSsize_t(i);
        if (!index) return -1;
        if (PyObject_SetItem(target, index, item) < 0)
        {
            Py_DECRF(index);
            return -1;
        }
        Py_DECREF(index);
    }
    return 0;
}

对于引用计数,函数的返回值有所不同。作为参数传递的时,引用的ownership多数情况下不会改变,但对于返回对象引用的函数,其会交出引用对象的ownership。理由很简单,在很多情况下,返回的对象是瞬间创建的,返回的对象引用也是唯一的,因此,返回对象引用的函数(如PyObject_GetItem()PySequence_GetItem())通常都是返回一个新的引用,调用者就成了该引用的唯一拥有者。

注意: 你是否用有函数返回的引用,只取决于你所调用的函数–– the plumage (the type of the object passed as an argument to the function) doesn’t enter into it!。如果你从一个list中用PyList_GetItem()提取一个item,你不会获得该item的引用,但是,如果你使用的是PySequence_GetItem() 进行同样的操作,则你会得到该item的引用。例子如下:

long sum_list(PyObject *list)
{
    Py_ssize_t i, n;
    long total = 0, value;
    PyObject *item;
    n = PyList_Size(list);
    if (n < 0) return -1; /*not a list*/
    fo (i = 0; i < n; ++i)
    {
        item = PyList_GetItem(list, i); /* cannot fail*/
        if (!PyLong_Check(item)) continue;
        value = PyLong_AsLong(item);
        if (value == -1 && PyErr_Occurred()) return -1; /*integer too big*/
        total += value;
    }
    return total;
}

long sum_sequence(PyObject *sequence)
{
    Py_ssize_t i, n;
    long total = 0, value;
    PyObject *item;
    n = PySequence_Length(sequence);
    if (n < 0) return -1; /*has no length*/
    for (i = 0; i < n; ++i)
    {
        item = PySequence_GetItem(sequence, i);
        if (item == NULL) return -1; /*not sequence, or other failure*/
        if (PyLong_Check(item))
        {
            value = PyLong_AsLong(item);
            Py_DECREF(item);
            if (value == -1 && PyErr_Occurred()) return -1;
            total += value;
        }
        else
        {
            Py_DECREF(item);
        }
    }
    return total;
}

3 异常处理

当一个函数发生错误时,它会丢弃其所有拥有对象的引用,并返回一个错误指示,如果文档没有明确指出,这个错误指示不是NULL就是-1,这取决与函数返回值的类型,一些函数还会返回Boolean型的true或false,其中false表示发生了错误。有一些函数不返回错误指示或返回一个二义性的值,这就需要调用函数PyErr_Occurred()来检查是否有错误发生。

在per-thread storage区维护有异常状态(在没有现成的程序中使用global storage区)。一个线程可以处于异常或无异常状态,函数PyErr_Occurred()用于检测线程处于哪个状态,若无异常,其返回NULL,否则返回异常对象的borrowed引用。很多函数都可以设置异常状态,其中最常用的是PyErr_SetString(),函数PyErr_Clear()用来清除异常状态。

完整的异常状态包含3个对象(都可以为NULL):异常type、对应的异常value和traceback信息,它们与sys.exc_info()返回的Python异常具有相同的含义,但是两者并不完全相同:后者返回的Python异常对象是Python中try … except …处理的最新的异常,然而,C语言级别的异常状态只有当一个在C函数间传递的异常到达Python字节码解释器的主循环时才会存在(the Python objects represent the last exception being handled by a Python try … except statement, while the C level exception state only exists while an exception is being passed on between C functions until it reaches the Python bytecode interpreter’s main loop, which takes care of transferring it to sys.exc_info() and friends.)

注意 :从Python1.5开始,在Python代码中访问异常状态首选的线程安全方式是调用函数sys.exc_info(),该函数为Python代码返回per-thread的异常状态。

首要准则:若函数A调用函数B来完成一些任务,函数A要检查函数B是否抛出了异常,这样的话,异常就会传递给函数A,函数A要丢弃其拥有的所有对象引用并返回一个错误指示但不能再设置另外的异常,因为新的异常会覆盖函数B刚抛出的异常致使错误信息丢失。

下面是一个处理异常的例子

def incr_item(dict, key):
    try:
        item = dict[key]
    except KeyError:
        item = 0
    dict[key] = item + 1
int incr_item(PyObject *dict, PyObject *key)
{
    /* Objects all initialized to NULL for Py_XDECREF */
    PyObject *item = NULL, *const_one = NULL, *incremented_item = NULL;
    int rv = -1;
    item = PyObject_GetItem(dict, key);
    if (item == NULL)
    {
        if (!PyErr_ExceptionMatches(PyExc_KeyError)) goto error;
        PyErr_Clear();
        item = PyLong_FromLong(0L);
        if (item == NULL) goto error;
    }
    const_one = PyLong_FromLong(1L);
    if (const_one == NULL) goto error;
    incremented_item = PyNumber_Add(item, const_one);
    if (incremented_item == NULL) goto error;
    if (PyObject_SetItem(dict, key, incremented_item) < 0) goto error;
    rv = 0;
error:
    Py_XDECREF(item);
    Py_XDECREF(const_one);
    Py_XDECREF(incremented_item);
    return rv;
}

注意函数PyErr_ExceptionMatches()PyErr_Clear()
Py_XDECREF()Py_DECREF() 有所不同,当Py_DECREF() 处理NULL引用是会crash,而Py_XDECREF() 却不会。

4 嵌入Python

几乎所有的功能都是在解释器初始化后才能使用的,函数Py_Initialize()是基本的初始化方法,其初始化被加载模块的符号表、创建模块builtins、_ _main_ _、sys,也包括sys.path。

Py_Initialize() 不会设置脚本参数列表(sys.argv),若需要设置该变量,需要在Py_Initialize() 之后显示的调用函数PySys_SetArgvEx(argc, argv, updatepath)

在多数系统中,Py_Initialize()根据标准Python解释器可执行文件所在目录来计算模块搜索目录,其假设Python库目录相对Python解释器目录的位置是固定的,特别的,它会搜索一个叫lib/pythonX.Y的目录,该目录位于shell的环境变量PATH中python命令所在目录的父目录中。

如果Python的可执行文件目录为/usr/local/bin/python,那么库目录就设为/usr/local/lib/pythonX.Y(实际上,这个目录也是在PATH中找不到python时的保底目录)。The user can override this behavior by setting the environment variable PYTHONHOME, or insert additional directories in front of the standard path by setting PYTHONPATH

程序可以在Py_Initialize() 之前调用Py_SetProgramName(file) 来steer搜索过程。注意,环境变量PYTHONPATHPYTHONHOME 依然有效。若程序想要完全控制该过程,需要重写函数Py_GetPath()Py_GetPrefix()Py_GetExecPrefix()Py_GetProgramFullPath(),这些函数全部在文件Modules/getpath.c 中定义。

函数Py_Finalize() 可反初始化Python,并释放部分内存空间(不是全部,如扩展模块分配的空间就不能释放),函数Py_IsInitialized() 检查Python是否已经初始化,返回true表示已经初始化。

5 调试

Python可以通过几个宏对解释器和扩展模块加载一些额外的检查,这些检查在运行时会带来较大的开销,所以默认不会加载。

在Python source distribution的Misc/SpecialBuilds.txt中列出了所有的debugging builds变量类型。Builds支持引用计数跟踪、内存分配器调试、主解释器循环低级别分析。

编译解释器时,若启用宏Py_DEBUG会生成python的调试版本,在Unix系统中,为./configure命令添加参数- -with-pydebug可以达到和宏Py_DEBUG 同样的效果。启用宏Py_DEBUG后,通用调试宏_DEBUG也会被启用,在Unix中,编译器的优化编译功能也会关闭。

启用宏Py_TRACE_REFS 后,启用引用跟踪,每个PyObject都会维护一个循环双端队列来保存active的对象,退出时,所有现存的引用都将被打印出来,启用Py_TRACE_REFS 后,宏Py_DEBUG 也会启用。

6 稳定的ABI

一般来说,Python/C API会随着每个发行版而改变,大多数改变都是向下兼容的,一般都是仅仅添加新的API而不是修改或删除现有的API。可是,API的兼容性并没有延伸至二进制级别(ABI),原因只要是struct的修改,如新添一项或修改某项的类型,这不会影响API但会影响ABI,这就导致扩展模块在每次Python更新之后都要重新编译。

从Python3.2开始,API的一个子集已经保证了一个稳定的ABI,若扩展模块想要使用这个稳定子集的API,需要启用宏Py_LIMITED_API,这样3.x(x >= 2)版本的扩展模块就不用重新编译了。

在一些情况下,稳定额ABI需要用到一些函数,扩展函数需要将宏Py_LIMITED_API 设置为PY_VERSION_HEX,后者是用户期望支持的最低版本(如0x03030000表示Python3.3),这些模块在往后的新版本上会正常工作,但对于老一些的版本就不行了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值