IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    扩展和嵌入Python解释器 Extending and Embedding the Python Interpreter

    gashero的Geek前哨发表于 2015-07-31 14:06:22
    love 0

    2007年开始使用Python与C的交互编程,那时分享了一篇《使用C/C++扩展Python》 http://gashero.yeax.com/?p=38 。8年过去了,很多技术时过境迁,但Python的扩展和嵌入技术仍然有很强大的生命力。尤其是国内外开始广泛的将Python引入科学计算、计算机视觉、深度学习等领域后。对Python的灵活性以及提高性能有着更高的需求。所以再次把我翻译的最新版本的文档分享出来。总计接近7万字(unicode字符),已经将原文档接近于翻译完成,剩余部分大多不是必需功能。

     

    扩展和嵌入Python解释器 Extending and Embedding the Python Interpreter

    作者:

    Guido van Rossum

    翻译:

    gashero

    版本:

    Release 2.4.4 2006-10-18

    日期:

    2007-08-14

    地址:

    http://docs.python.org/2.5/ext/ext.html

    目录

    • 1   使用C/C++扩展Python
      • 1.1   一个简单的例子
      • 1.2   关于错误和异常
      • 1.3   回到例子
      • 1.4   模块方法表和初始化函数
      • 1.5   编译和连接
      • 1.6   在C中调用Python函数
      • 1.7   解析传给扩展模块函数的参数
      • 1.8   解析传给扩展模块函数的关键字参数
      • 1.9   构造任意值
      • 1.10   引用计数
        • 1.10.1   Python中的引用计数
        • 1.10.2   拥有规则
        • 1.10.3   危险的薄冰
        • 1.10.4   NULL指针
      • 1.11   使用C++编写扩展
      • 1.12   提供给其他模块以C API
    • 2   定义新类型
      • 2.1   基础
        • 2.1.1   给例子添加数据和方法
        • 2.1.2   为数据属性提供控制
        • 2.1.3   支持循环引用垃圾收集
        • 2.1.4   继承其他类型
      • 2.2   类型方法
        • 2.2.1   销毁与释放
        • 2.2.2   对象表达
        • 2.2.3   属性管理
        • 2.2.4   对象比较
        • 2.2.5   抽象协议支持
        • 2.2.6   弱引用支持
        • 2.2.7   更多建议
    • 3   使用distutils构建扩展
      • 3.1   发布你的扩展模块
    • 4   在Windows构建扩展
      • 4.1   照着菜谱走猫步(a cookbook approach)
      • 4.2   Unix与Windows的不同
      • 4.3   实践中使用DLL
    • 5   嵌入Python到其他应用
      • 5.1   高层次嵌入
      • 5.2   超越高层嵌入:预览
      • 5.3   纯扩展
      • 5.4   扩展嵌入的Python
      • 5.5   在C++中嵌入Python
      • 5.6   链接必备条件

    1   使用C/C++扩展Python

    如果你会用C,实现Python嵌入模块很简单。利用扩展模块可做很多Python不方便做的事情,他们可以直接调用C库和系统调用。

    为了支持扩展,Python API定义了一系列函数、宏和变量,提供了对Python运行时系统的访问支持。Python的C API由C源码组成,并包含 "Python.h" 头文件。

    编写扩展模块与你的系统相关,下面会详解。

    1.1   一个简单的例子

    下面的例子创建一个叫做 "spam" 的扩展模块,调用C库函数 system() 。这个函数输入一个NULL结尾的字符串并返回整数,可供Python调用方式如下:

    >>> import spam

    >>> status=spam.system("ls -l")

    一个C扩展模块的文件名可以直接是 模块名.c 或者是 模块名module.c 。第一行应该导入头文件:

    #include <Python.h>

    这会导入Python API。

    Warning

    因为Python含有一些预处理定义,所以你必须在所有非标准头文件导入之前导入Python.h 。

    Python.h中所有用户可见的符号都有 Py 或 PY 的前缀,除非定义在标准头文件中。为了方便 "Python.h" 也包含了一些常用的标准头文件,包括<stdio.h>,<string.h>,<errno.h>,<stdlib.h>。如果你的系统没有后面的头文件,则会直接定义函数 malloc() 、 free() 和 realloc() 。

    下面添加C代码到扩展模块,当调用 "spam.system(string)" 时会做出响应:

    static PyObject*

    spam_system(PyObject* self, PyObject* args) {

        const char* command;

        int sts;

        if (!PyArg_ParseTuple(args,"s",&command))

            return NULL;

        sts=system(command);

        return Py_BuildValue("i",sts);

    }

    调用方的Python只有一个命令参数字符串传递到C函数。C函数总是有两个参数,按照惯例分别叫做 self 和 args 。

    self 参数仅用于用C实现内置方法而不是函数。本例中, self 总是为NULL,因为我们定义的是个函数,不是方法。这一切都是相同的,所以解释器也就不需要刻意区分两种不同的C函数。

    args 参数是一个指向Python的tuple对象的指针,包含参数。每个tuple子项对应一个调用参数。这些参数也全都是Python对象,所以需要先转换成C值。函数 PyArg_ParseTuple() 检查参数类型并转换成C值。它使用模板字符串检测需要的参数类型。

    PyArg_ParseTuple() 正常返回非零,并已经按照提供的地址存入了各个变量值。如果出错(零)则应该让函数返回NULL以通知解释器出错。

    1.2   关于错误和异常

    一个常见惯例是,函数发生错误时,应该设置一个异常环境并返回错误值(NULL)。异常存储在解释器静态全局变量中,如果为NULL,则没有发生异常。异常的第一个参数也需要保存在静态全局变量中,也就是raise的第二个参数。第三个变量包含栈回溯信息。这三个变量等同于Python变量 sys.exc_type 、 sys.exc_value 、 sys.exc_traceback 。这对找到错误是很必要的。

    Python API中定义了一些函数来设置这些变量。

    最常用的就是 PyErr_SetString() 。参数是异常对象和C字符串。异常对象一般由像 PyExc_ZeroDivisionError 这样的对象来预定义。C字符串指明异常原因,并最终存储在异常的第一个参数里面。

    另一个有用的函数是 PyErr_SetFromErrno() ,仅接受一个异常对象,异常描述包含在全局变量 errno 中。最通用的函数还是 PyErr_SetObject() ,包含两个参数,分别为异常对象和异常描述。你不需要使用 Py_INCREF() 来增加传递到其他函数的参数对象的引用计数。

    你可以通过 PyErr_Occurred() 获知当前异常,返回当前异常对象,如果确实没有则为NULL。一般来说,你在调用函数时不需要调用 PyErr_Occurred() 检查是否发生了异常,你可以直接检查返回值。

    如果调用更下层函数时出错了,那么本函数返回NULL表示错误,并且整个调用栈中只要有一处调用 PyErr_*() 函数设置异常就可以。一般来说,首先发现错误的函数应该设置异常。一旦这个错误到达了Python解释器的主循环,则会中断当前执行代码并追究异常。

    有一种情况下,模块可能依靠其他 PyErr_*() 函数给出更加详细的错误信息,并且是正确的。但是按照一般规则,这并不重要,很多操作都会因为种种原因而挂掉。

    想要忽略这些函数设置的异常,异常情况必须明确的使用 PyErr_Clear() 来清除。只有在C代码想要自己处理异常而不是传给解释器时才这么做。

    每次失败的 malloc() 调用必须抛出一个异常,直接调用 malloc() 或 realloc() 的地方要调用 PyErr_NoMemory() 并返回错误。所有创建对象的函数都已经实现了这个异常的抛出,所以这是每个分配内存都要做的。

    还要注意的是 PyArg_ParseTuple() 系列函数的异常,返回一个整数状态码是有效的,0是成功,-1是失败,有如Unix系统调用。

    最后,小心垃圾情理,也就是 Py_XDECREF() 和 Py_DECREF() 的调用,会返回的异常。

    选择抛出哪个异常完全是你的个人爱好了。有一系列的C对象代表了内置Python异常,例如 PyExc_ZeroDivisionError ,你可以直接使用。当然,你可能选择更合适的异常,不过别使用 PyExc_TypeError 告知文件打开失败(有个更合适的 PyExc_IOError )。如果参数列表有误, PyArg_ParseTuple() 通常会抛出 PyExc_TypeError 。如果参数值域有误, PyExc_ValueError 更合适一些。

    你也可以为你的模块定义一个唯一的新异常。需要在文件前部声明一个静态对象变量,如:

    static PyObject* SpamError;

    然后在模块初始化函数(initspam())里面初始化它,并省却了处理:

    PyMODINIT_FUNC

    initspam(void) {

        PyObject* m;

        m=Py_InitModule("spam",SpamMethods);

        if (m==NULL)

            return NULL;

        SpamError=PyErr_NewException("spam.error",NULL,NULL);

        Py_INCREF(SpamError);

        PyModule_AddObject(m,"error",SpamError);

    }

    注意实际的Python异常名字是 spam.error 。 PyErr_NewException() 函数使用Exception为基类创建一个类(除非是使用另外一个类替代NULL)。

    同样注意的是创建类保存了SpamError的一个引用,这是有意的。为了防止被垃圾回收掉,否则SpamError随时会成为野指针。

    一会讨论 PyMODINIT_FUNC 作为函数返回类型的用法。

    1.3   回到例子

    回到前面的例子,你应该明白下面的代码:

    if (!PyArg_ParseTuple(args,"s",&command))

        return NULL;

    就是为了报告解释器一个异常。如果执行正常则变量会拷贝到本地,后面的变量都应该以指针的方式提供,以方便设置变量。本例中的command会被声明为 "const char* command" 。

    下一个语句使用UNIX系统函数system(),传递给他的参数是刚才从 PyArg_ParseTuple() 取出的:

    sts=system(command);

    我们的 spam.system() 函数必须返回一个PY对象,这可以通过 Py_BuildValue() 来完成,其形式与 PyArg_ParseTuple() 很像,获取格式字符串和C值,并返回新的Python对象:

    return Py_BuildValue("i",sts);

    在这种情况下,会返回一个整数对象,这个对象会在Python堆里面管理。

    如果你的C函数没有有用的返回值,则必须返回None。你可以用 Py_RETUN_NONE 宏来完成:

    Py_INCREF(Py_None);

    return Py_None;

    Py_None 是一个C名字指定Python对象None。这是一个真正的PY对象,而不是NULL指针。

    1.4   模块方法表和初始化函数

    把函数声明为可以被Python调用,需要先定义一个方法表:

    static PyMethodDef SpamMethods[]= {

        ...

        {"system",spam_system,METH_VARARGS,

        "Execute a shell command."},

        ...

        {NULL,NULL,0,NULL}    /*必须的结束符*/

    };

    注意第三个参数 METH_VARARGS ,这个标志指定会使用C的调用惯例。可选值有 METH_VARARGS 、 METH_VARARGS | METH_KEYWORDS 。值0代表使用 PyArg_ParseTuple() 的陈旧变量。

    如果单独使用 METH_VARARGS ,函数会等待Python传来tuple格式的参数,并最终使用 PyArg_ParseTuple() 进行解析。

    METH_KEYWORDS 值表示接受关键字参数。这种情况下C函数需要接受第三个 PyObject* 对象,表示字典参数,使用 PyArg_ParseTupleAndKeywords() 来解析出参数。

    方法表必须传递给模块初始化函数。初始化函数函数名规则为 initname() ,其中 name 为模块名。并且不能定义为文件中的static函数:

    PyMODINIT_FUNC

    initspam(void) {

        (void) Py_InitModule("spam",SpamMethods);

    }

    注意 PyMODINIT_FUNC 声明了void为返回类型,还有就是平台相关的一些定义,如C++的就要定义成 extern "C" 。

    Python程序首次导入这个模块时就会调用initspam()函数。他调用 Py_InitModule() 来创建一个模块对象,同时这个模块对象会插入到 sys.modules 字典中的 "spam" 键下面。然后是插入方法表中的内置函数到 "spam" 键下面。 Py_InitModule() 返回一个指针指向刚创建的模块对象。他是有可能发生严重错误的,也有可能在无法正确初始化时返回NULL。

    当嵌入Python时, initspam() 函数不会自动被调用,除非在入口处的 _PyImport_Inittab 表。最简单的初始化方法是在 Py_Initialize() 之后静态调用 initspam() 函数:

    int

    main(int argc, char* argv[]) {

        Py_SetProgramName(argv[0]);

        Py_Initialize();

        initspam();

        //...

    }

    在Python发行版的 Demo/embed/demo.c 中有可以参考的源码。

    Note

    从 sys.modules 中移除模块入口,或者在多解释器环境中导入编译模块,会导致一些扩展模块出错。扩展模块作者应该特别注意初始化内部数据结构。同时要注意 reload() 函数可能会被用在扩展模块身上,并调用模块初始化函数,但是对动态状如对象(动态链接库),却不会重新载入。

    更多关于模块的现实的例子包含在Python源码包的Modules/xxmodule.c中。这些文件可以用作你的代码模板,或者学习。脚本 modulator.py 包含在源码发行版或Windows安装中,提供了一个简单的GUI,用来声明需要实现的函数和对象,并且可以生成供填入的模板。脚本在 Tools/modulator/ 目录。查看README以了解用法。

    1.5   编译和连接

    如果使用动态载入,细节依赖于系统,查看关于构建扩展模块部分,和关于在Windows下构建扩展的细节。

    如果你无法使用动态载入,或者希望模块成为Python的永久组成部分,就必须改变配置并重新构建解释器。幸运的是,这对UNIX来说很简单,只要把你的代码(例如spammodule.c)放在 Modules/ Python源码目录下,然后增加一行到文件 Modules/Setup.local 来描述你的文件即可:

    spam spammodule.o

    然后重新构建解释器,使用make。你也可以在 Modules/ 子目录使用make,但是你接下来首先要重建Makefile文件,使用 make Makefile 命令。这对你改变 Setup 文件来说很重要。

    如果你的模块需要其他扩展模块连接,则需要在配置文件后面加入,如:

    spam spammodule.o -lX11

    1.6   在C中调用Python函数

    迄今为止,我们一直把注意力集中于让Python调用C函数,其实反过来也很有用,就是用C调用Python函数。这在回调函数中尤其有用。如果一个C接口使用回调,那么就要实现这个回调机制。

    幸运的是,Python解释器是比较方便回调的,并给标准Python函数提供了标准接口。这里就不再详述解析Python代码作为输入的方式,如果有兴趣可以参考 Python/pythonmain.c 中的 -c 命令代码。

    调用Python函数,首先Python程序要传递Python函数对象。当调用这个函数时,用全局变量保存Python函数对象的指针,还要调用 Py_INCREF() 来增加引用计数,当然不用全局变量也没什么关系。例如如下:

    static PyObject* my_callback=NULL;

    static PyObject*

    my_set_callback(PyObject* dummy, PyObject* args) {

        PyObject* result=NULL;

        PyObject* temp;

        if (PyArg_ParseTuple(args,"O:set_callback",&temp)) {

            if (!PyCallable_Check(temp)) {

                PyErr_SetString(PyExc_TypeError,"parameter must be callable");

                return NULL;

            }

            Py_XINCREF(temp);

            Py_XINCREF(my_callback);

            my_callback=temp;

            Py_INCREF(Py_None);

            result=Py_None;

        }

        return result;

    }

    这个函数必须使用 METH_VARARGS 标志注册到解释器。宏 Py_XINCREF() 和 Py_XDECREF() 增加和减少对象的引用计数。

    然后,就要调用函数了,使用 PyEval_CallObject() 。这个函数有两个参数,都是指向Python对象:Python函数和参数列表。参数列表必须总是tuple对象,如果没有参数则要传递空的tuple。使用 Py_BuildValue() 时,在圆括号中的参数会构造成tuple,无论有没有参数,如:

    int arg;

    PyObject* arglist;

    PyObject* result;

    //...

    arg=123;

    //...

    arglist=Py_BuildValue("(i)",arg);

    result=PyEval_CallObject(my_callback,arglist);

    Py_DECREF(arglist);

    PyEval_CallObject() 返回一个Python对象指针表示返回值。 PyEval_CallObject() 是 引用计数无关 的,有如例子中,参数列表对象使用完成后就立即减少引用计数了。PyEval_CallObject() 返回一个Python对象指针表示返回值。 PyEval_CallObject() 是 引用计数无关 的,有如例子中,参数列表对象使用完成后就立即减少引用计数了。

    PyEval_CallObject() 的返回值总是新的,新建对象或者是对已有对象增加引用计数。所以你必须获取这个对象指针,在使用后减少其引用计数,即便是对返回值没有兴趣也要这么做。但是在减少这个引用计数之前,你必须先检查返回的指针是否为NULL。如果是NULL,则表示出现了异常并中止了。如果没有处理则会向上传递并最终显示调用栈,当然,你最好还是处理好异常。如果你对异常没有兴趣,可以用 PyErr_Clear() 清除异常,例如:

    if (result==NULL)

        return NULL;  /*向上传递异常*/

    //使用result

    Py_DECREF(result);

    依赖于具体的回调函数,你还要提供一个参数列表到 PyEval_CallObject() 。在某些情况下参数列表是由Python程序提供的,通过接口再传到回调函数。这样就可以不改变形式直接传递。另外一些时候你要构造一个新的tuple来传递参数。最简单的方法就是 Py_BuildValue() 函数构造tuple。例如,你要传递一个事件对象时可以用:

    PyObject* arglist;

    //...

    arglist=Py_BuildValue("(l)",eventcode);

    result=PyEval_CallObject(my_callback,arglist);

    Py_DECREF(arglist);

    if (result==NULL)

        return NULL;  /*一个错误*/

    /*使用返回值*/

    Py_DECREF(result);

    注意 Py_DECREF(arglist) 所在处会立即调用,在错误检查之前。当然还要注意一些常规的错误,比如 Py_BuildValue() 可能会遭遇内存不足等等。

    1.7   解析传给扩展模块函数的参数

    函数 PyArg_ParseTuple() 声明如下:

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

    参数 arg 必须是一个tuple对象,包含传递过来的参数, format 参数必须是格式化字符串,语法解释见 "Python C/API" 的5.5节。剩余参数是各个变量的地址,类型要与格式化字符串对应。

    注意 PyArg_ParseTuple() 会检测他需要的Python参数类型,却无法检测传递给他的C变量地址,如果这里出错了,可能会在内存中随机写入东西,小心。

    任何Python对象的引用,在调用者这里都是 借用的引用 ,而不增加引用计数。

    一些例子:

    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);

    /* python call: f('whoops!') */

     

    ok=PyArg_ParseTuple(args,"lls",&k,&l,&s);

    /* python call: f(1,2,'three') */

     

    ok=PyArg_ParseTuple(args,"(ii)s#",&i,&j,&s,&size);

    /* python call: f((1,2),'three') */

     

    {

        const char* file;

        const char* mode="r";

        int bufsize=0;

        ok=PyArg_ParseTuple(args,"s|si",&file,&mode,&bufsize);

        /* python call:

            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);

        /* python call: f(((0,0),(400,300)),(10,10)) */

    }

     

    {

        Py_complex c;

        ok=PyArg_ParseTuple(args,"D:myfunction",&c);

        /* python call: myfunction(1+2j) */

    }

    1.8   解析传给扩展模块函数的关键字参数

    函数 PyArg_ParseTupleAndKeywords() 声明如下:

    int PyArg_ParseTupleAndKeywords(PyObject* arg, PyObject* kwdict, \

        char* format, char* kwlist[],...);

    参数arg和format定义同 PyArg_ParseTuple() 。参数 kwdict 是关键字字典,用于接受运行时传来的关键字参数。参数 kwlist 是一个NULL结尾的字符串,定义了可以接受的参数名,并从左到右与format中各个变量对应。如果执行成功 PyArg_ParseTupleAndKeywords() 会返回true,否则返回false并抛出异常。

    Note

    嵌套的tuple在使用关键字参数时无法生效,不在kwlist中的关键字参数会导致 TypeError 异常。

    如下是使用关键字参数的例子模块,作者是 Geoff Philbrick (phibrick@hks.com):

    #include "Python.h"

     

    static PyObject*

    keywdarg_parrot(PyObject* self, PyObject* args, PyObject* keywds) {

        int voltage;

        char* state="a stiff";

        char* action="voom";

        char* type="Norwegian Blue";

        static char* kwlist[]={"voltage","state","action","type",NULL};

        if (!PyArg_ParseTupleAndKeywords(args,keywds,"i|sss",kwlist,

                &voltage,&state,&action,&type))

            return NULL;

        printf("-- This parrot wouldn't %s if you put %i Volts through it.\n",action,voltage);

        printf("-- Lovely plumage, the %s -- It's %s!\n",type,state);

        Py_INCREF(Py_None);

        return Py_None;

    }

     

    static PyMethodDef keywdary_methods[]= {

        /*注意PyCFunction,这对需要关键字参数的函数很必要*/

        {"parrot",(PyCFunction)keywdarg_parrot, \

            METH_VARARGS | METH_KEYWORDS, \

            "Print a lovely skit to standard output."},

        {NULL,NULL,0,NULL}

    };

     

    void

    initkeywdarg(void) {

        Py_InitModule("keywdarg",keywdarg_methods);

    }

    1.9   构造任意值

    这个函数声明与 PyArg_ParseTuple() 很相似,如下:

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

    接受一个格式字符串,与 PyArg_ParseTuple() 相同,但是参数必须是原变量的地址指针。最终返回一个Python对象适合于返回给Python代码。

    一个与 PyArg_ParseTuple() 的不同是,后面可能需要的要求返回一个tuple,比如用于传递给其他Python函数以参数。 Py_BuildValue() 并不总是生成tuple,在多于1个参数时会生成tuple,而如果没有参数则返回None,一个参数则直接返回该参数的对象。如果要求强制生成一个长度为空的tuple,或包含一个元素的tuple,需要在格式字符串中加上括号。

    例如:

    代码

    返回值

    Py_BuildValue("")

    None

    Py_BuildValue("i",123)

    123

    Py_BuildValue("iii",123,456,789)

    (123,456,789)

    Py_BuildValue("s","hello")

    'hello'

    Py_BuildValue("ss","hello","world")

    ('hello', 'world')

    Py_BuildValue("s#","hello",4)

    'hell'

    Py_BuildValue("()")

    ()

    Py_BuildValue("(i)",123)

    (123,)

    Py_BuildValue("(ii)",123,456)

    (123,456)

    Py_BuildValue("(i,i)",123,456)

    (123,456)

    Py_BuildValue("[i,i]",123,456)

    [123,456]

    Py_BuildValue("{s:i,s:i}",'a',1,'b',2)

    {'a':1,'b':2}

    Py_BuildValue("((ii)(ii))(ii)",1,2,3,4,5,6)

    (((1,2),(3,4)),(5,6))

    1.10   引用计数

    在C/C++语言中,程序员负责动态分配和回收堆(heap)当中的内存。这意味着,我们在C中编程时必须面对这个问题。

    每个由 malloc() 分配的内存块,最终都要由 free() 扔到可用内存池里面去。而调用 free() 的时机非常重要,如果一个内存块忘了 free() 则是内存泄漏,程序结束前将无法重新使用。而如果对同一内存块 free() 了以后,另外一个指针再次访问,则叫做野指针。这同样会导致严重的问题。

    内存泄露往往发生在一些并不常见的程序流程上面,比如一个函数申请了资源以后,却提前返回了,返回之前没有做清理工作。人们经常忘记释放资源,尤其对于后加新加的代码,而且会长时间都无法发现。这些函数往往并不经常调用,而且现在大多数机器都有庞大的虚拟内存,所以内存泄漏往往在长时间运行的进程,或经常被调用的函数中才容易发现。所以最好有个好习惯加上代码约定来尽量避免内存泄露。

    Python往往包含大量的内存分配和释放,同样需要避免内存泄漏和野指针。他选择的方法就是 引用计数 。其原理比较简单:每个对象都包含一个计数器,计数器的增减与引用的增减直接相关,当引用计数为0时,表示对象已经没有存在的意义了,就可以删除了。

    一个叫法是 自动垃圾回收 ,引用计数是一种垃圾回收方法,用户必须要手动调用 free() 函数。优点是可以提高内存使用率,缺点是C语言至今也没有一个可移植的自动垃圾回收器。引用计数却可以很好的移植,有如C当中的 malloc() 和 free() 一样。也许某一天会出现C语言饿自动垃圾回收器,不过在此之前我们还得用引用计数。

    Python使用传统的引用计数实现,不过他包含一个循环引用探测器。这允许应用不需要担心的直接或间接的创建循环引用,而这实际上是引用计数实现的自动垃圾回收的致命缺点。循环引用指对象经过几层引用后回到自己,导致了其引用计数总是不为0。传统的引用计数实现无法解决循环引用的问题,尽管已经没有其他外部引用了。

    循环引用探测器可以检测出垃圾回收中的循环并释放其中的对象。只要Python对象有 __del__() 方法,Python就可以通过 gc module 模块来自动暴露出循环引用。gc模块还提供 collect() 函数来运行循环引用探测器,可以在配置文件或运行时禁用循环应用探测器。

    循环引用探测器作为一个备选选项,默认是打开的,可以在构建时使用 --without-cycle-gc 选项加到 configure 上来配置,或者移除 pyconfig.h 文件中的 WITH_CYCLE_GC 宏定义。在循环引用探测器禁用后,gc模块将不可用。

    1.10.1   Python中的引用计数

    有两个宏 Py_INCREF(x) 和 Py_DECREF(x) 用于增减引用计数。 Py_DECREF() 同时会在引用计数为0时释放对象资源。为了灵活性,他并不是直接调用 free() 而是调用对象所在类型的析构函数。

    一个大问题是何时调用 Py_INCREF(x) 和 Py_DECREF(x) 。首先介绍一些术语。没有任何人都不会 拥有 一个对象,只能拥有其引用。对一个对象的引用计数定义了引用数量。拥有的引用,在不再需要时负责调用 Py_DECREF() 来减少引用计数。传递引用计数有三种方式:传递、存储和调用 Py_DECREF() 。忘记减少拥有的引用计数会导致内存泄漏。

    同样重要的一个概念是 借用 一个对象,借用的对象不能调用 Py_DECREF() 来减少引用计数。借用者在不需要借用时,不保留其引用就可以了。应该避免拥有者释放对象之后仍然访问对象,也就是野指针。

    借用的优点是你无需管理引用计数,缺点是可能被野指针搞的头晕。借用导致的野指针问题常发生在看起来无比正确,但是事实上已经被释放的对象。

    借用的引用也可以用 Py_INCREF() 来改造成拥有的引用。这对引用的对象本身没什么影响,但是拥有引用的程序有责任在适当的时候释放这个拥有。

    1.10.2   拥有规则

    一个对象的引用进出一个函数时,其引用计数也应该同时改变。

    大多数函数会返回一个对对象拥有的引用。而且几乎所有的函数其实都会创建一个对象,例如 PyInt_FromLong() 和 Py_BuildValue() ,传递一个拥有的引用给接受者。即便不是刚创建的,你也需要接受一个新的拥有引用。一般来说, PyInt_FromLong() 会维护一个常用值缓存,并且返回缓存项的引用。

    很多函数提取一些对象的子对象并传递拥有引用,例如 PyObject_GetAttrString() 。另外,小心一些函数,包括: PyTuple_GetItem() 、 PyList_GetItem() 、 PyDict_GetItem() 和 PyDict_GetItemString() ,他们返回的都是借用的引用。

    函数 PyImport_AddModule() 也是返回借用的引用,尽管他实际上创建了对象,只不过其拥有的引用实际存储在了 sys.modules 中。

    当你传递一个对象的引用到另外一个函数时,一般来说,函数是借用你的引用,如果他确实需要存储,则会使用 Py_INCREF() 来变为拥有引用。这个规则有两种可能的异常: PyTuple_SetItem() 和 PyList_SetItem() ,这两个函数获取传递给他的拥有引用,即便是他们执行出错了。不过 PyDict_SetItem() 却不是接收拥有的引用。

    当一个C函数被py调用时,使用对参数的借用。调用者拥有参数对象的拥有引用。所以,借用的引用的寿命是函数返回。只有当这类参数必须存储时,才会使用 Py_INCREF() 变为拥有的引用。

    从C函数返回的对象引用必须是拥有的引用,这时的拥有者是调用者。

    1.10.3   危险的薄冰

    有些使用借用的情况会出现问题。这是对解释器的盲目理解所导致的,因为拥有者往往提前释放了引用。

    首先而最重要的情况是使用 Py_DECREF() 来释放一个本来是借用的对象,比如列表中的元素:

    void

    bug(PyObject* list) {

        PyObject* item=PyList_GetItem(list,0);

        PyList_SetItem(list,1,PyInt_FromLong(0L));

        PyObject_Print(item,stdout,0); /* BUG! */

    }

    这个函数首先借用了 list[0] ,然后把 list[1] 替换为值0,最后打印借用的引用。看起来正确么,不是!

    我们来跟踪一下 PyList_SetItem() 的控制流,列表拥有所有元素的引用,所以当项目1被替换时,他就释放了原始项目1。而原始项目1是一个用户定义类的实例,假设这个类定义包含 __del__() 方法。如果这个类的实例引用计数为1,处理过程会调用 __del__() 方法。

    因为使用python编写,所以 __del__() 中可以用任何python代码来完成释放工作。替换元素的过程会执行 del list[0] ,即减掉了对象的最后一个引用,然后就可以释放内存了。

    知道问题后,解决方案就出来了:临时增加引用计数。正确的版本如下:

    void

    no_bug(PyObject* list) {

        PyObject* item=PyList_GetItem(list,0);

        Py_INCREF(item);

        PyList_SetItem(list,1,PyInt_FromLong(0L));

        PyObject_Print(item,stdout,0);

        Py_DECREF(item);

    }

    这是一个真实的故事,旧版本的Python中多处包含这个问题,让guido花费大量时间研究 __del__() 为什么失败了。

    第二种情况的问题出现在多线程中的借用引用。一般来说,python中的多线程之间并不能互相影响对方,因为存在一个GIL。不过,这可能使用宏 Py_BEGIN_ALLOW_THREADS 来临时释放锁,最后通过宏 Py_END_ALLOW_THREADS 来再申请锁,这在IO调用时很常见,允许其他线程使用处理器而不是等待IO结束。很明显,下面的代码与前面的问题相同:

    void

    bug(PyObject* list) {

        PyObject* item=PyList_GetItem(list,0);

        Py_BEGIN_ALLOW_THREADS

        //一些IO阻塞调用

        Py_END_ALLOW_THREADS

        PyObject_Print(item,stdout,0); /*BUG*/

    }

    1.10.4   NULL指针

    一般来说,函数接受的参数并不希望你传递一个NULL指针进来,这会出错的。函数的返回对象引用返回NULL则代表发生了异常。这是Python的机制,毕竟,一个函数如果执行出错了,那么也没有必要多解释了,浪费时间。(注:彪悍的异常也不需要解释)

    最好的测试NULL的方法就是在代码里面,一个指针如果收到了NULL,例如 malloc() 或其他函数,则表示发生了异常。

    宏 Py_INCREF() 和 Py_DECREF() 并不检查NULL指针,不过还好, Py_XINCREF() 和 Py_XDECREF() 会检查。

    检查特定类型的宏,形如 Pytype_Check() 也不检查NULL指针,因为这个检查是多余的。

    C函数的调用机制确保传递的参数列表(也就是args参数)用不为NULL,事实上,它总是一个tuple。

    而把NULL扔到Python用户那里可就是一个非常严重的错误了。

    1.11   使用C++编写扩展

    有时候需要用C++编写Python扩展模块。不过有一些严格的限制。如果Python解释器的主函数是使用C编译器编译和连接的,那么全局和静态对象的构造函数将无法使用。而主函数使用C++编译器时则不会有这个问题。被Python调用的函数,特别是模块初始化函数,必须声明为 extern "C" 。没有必要在Python头文件中使用 extern "C" 因为在使用C++编译器时会自动加上 __cplusplus 这个定义,而一般的C++编译器一般都会设置这个符号。

    1.12   提供给其他模块以C API

    很多模块只是提供给Python使用的函数和新类型,但是偶尔也有可能被其他扩展模块所调用。例如一个模块实现了 "collection" 类型,可以像list一样工作而没有顺序。有如标准Python中的list类型一样,提供的C接口可以让扩展模块创建和管理list,这个新的类型也需要有C函数以供其他扩展模块直接管理。

    初看这个功能可能以为很简单:只要写这些函数就行了(不需要声明为静态),提供适当的头文件,并注释C的API。当然,如果所有的扩展模块都是静态链接到Python解释器的话,这当然可以正常工作。但是当其他扩展模块是动态链接库时,定义在一个模块中的符号,可能对另外一个模块来说并不是可见的。而这个可见性又是依赖操作系统实现的,一些操作系统对Python解释器使用全局命名空间和所有的扩展模块(例如Windows),也有些系统则需要明确的声明模块的导出符号表(AIX就是个例子),或者提供一个不同策略的选择(大多数的Unices)。即便这些符号是全局可见的,拥有函数的模块,也可能尚未载入。

    为了可移植性,不要奢望任何符号会对外可见。这意味着模块中所有的符号都声明为 static ,除了模块的初始化函数以外,这也是为了避免各个扩展模块之间的符号名称冲突。这也意味着必须以其他方式导出扩展模块的符号。

    Python提供了一种特殊的机制,以便在扩展模块间传递C级别的信息(指针): CObject 。一个CObject是一个Python的数据类型,存储了任意类型指针(void*)。CObject可以只通过C API来创建和存取,但是却可以像其他Python对象那样来传递。在特别的情况下,他们可以被赋予一个扩展模块命名空间内的名字。其他扩展模块随后可以导入这个模块,获取这个名字的值,然后得到CObject中保存的指针。

    通过CObject有很多种方式导出扩展模块的C API。每个名字都可以得到他自己的CObject,或者可以把所有的导出C API放在一个CObject指定的数组中来发布。所以可以有很多种方法导出C API。

    如下的示例代码展示了把大部分的重负载任务交给扩展模块,作为一个很普通的扩展模块的例子。他保存了所有的C API的指针到一个数组中,而这个数组的指针存储在CObject中。对应的头文件提供了一个宏以管理导入模块和获取C API的指针,客户端模块只需要在存取C API之前执行这个宏就可以了。

    这个导出模块是修改自1.1节的spam模块。函数 spam.system() 并不是直接调用C库的函数 system() ,而是调用 PySpam_System() ,提供了更加复杂的功能。这个函数 PySpam_System() 同样导出供其他扩展模块使用。

    函数 PySpam_System() 是一个纯C函数,声明为static如下:

    static int

    PySpam_System(const char* command) {

        return system(command);

    }

    函数 spam_system() 做了细小的修改:

    static PyObject*

    spam_system(PyObject* self, PyObject* args) {

        const char* command;

        int sts;

        if (!PyArg_ParseTuple(args,"s",&command))

            return NULL;

        sts=PySpam_System(command);

        return Py_BuildValue("i",sts);

    }

    在模块的头部加上如下行:

    #include "Python.h"

    另外两行需要添加的是:

    #define SPAM_MODULE

    #include "spammodule.h"

    这个宏定义是告诉头文件需要作为导出模块,而不是客户端模块。最终模块的初始化函数必须管理初始化C API指针数组的初始化:

    PyMODINIT_FUNC

    initspam(void)

    {

        PyObject *m;

        static void *PySpam_API[PySpam_API_pointers];

        PyObject *c_api_object;

     

        m = Py_InitModule("spam", SpamMethods);

        if (m == NULL)

            return;

     

        /* Initialize the C API pointer array */

        PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;

     

        /* Create a CObject containing the API pointer array's address */

        c_api_object = PyCObject_FromVoidPtr((void *)PySpam_API, NULL);

     

        if (c_api_object != NULL)

            PyModule_AddObject(m, "_C_API", c_api_object);

    }

    注意 PySpam_API 声明为static,否则 initspam() 函数执行之后,指针数组就消失了。

    大部分的工作还是在头文件 spammodule.h 中,如下:

    #ifndef Py_SPAMMODULE_H

    #define Py_SPAMMODULE_H

    #ifdef __cplusplus

    extern "C" {

    #endif

     

    /* Header file for spammodule */

     

    /* C API functions */

    #define PySpam_System_NUM 0

    #define PySpam_System_RETURN int

    #define PySpam_System_PROTO (const char *command)

     

    /* Total number of C API pointers */

    #define PySpam_API_pointers 1

     

     

    #ifdef SPAM_MODULE

    /* This section is used when compiling spammodule.c */

     

    static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;

     

    #else

    /* This section is used in modules that use spammodule's API */

     

    static void **PySpam_API;

     

    #define PySpam_System \

     (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])

     

    /* Return -1 and set exception on error, 0 on success. */

    static int

    import_spam(void)

    {

        PyObject *module = PyImport_ImportModule("spam");

     

        if (module != NULL) {

            PyObject *c_api_object = PyObject_GetAttrString(module, "_C_API");

            if (c_api_object == NULL)

                return -1;

            if (PyCObject_Check(c_api_object))

                PySpam_API = (void **)PyCObject_AsVoidPtr(c_api_object);

            Py_DECREF(c_api_object);

        }

        return 0;

    }

     

    #endif

     

    #ifdef __cplusplus

    }

    #endif

     

    #endif /* !defined(Py_SPAMMODULE_H) */

    想要调用 PySpam_System() 的客户端模块必须在初始化函数中调用 import_spam() 以初始化导出扩展模块:

    PyMODINIT_FUNC

    initclient(void) {

        PyObject* m;

        m=Py_InitModule("client",ClientMethods);

        if (m==NULL)

            return;

        if (import_spam()<0)

            return;

        /*其他初始化语句*/

    }

    这样做的缺点是 spammodule.h 有点复杂。不过这种结构却可以方便的用于其他导出函数,所以学着用一次也就好了。

    最后需要提及的是CObject提供的一些附加函数,用于CObject指定的内存块的分配和释放。详细信息可以参考Python的C API参考手册的CObject一节,和CObject的实现,参考文件 Include/cobject.h 和 Objects/cobject.c 。

    2   定义新类型

    程序员可以自定义类型供Python操作,有如一些核心数据类型一样。

    这并不困难,所有的扩展类型代码都遵循同一个模式,但是有一些细节需要了解就是了。

    Note

    定义新类型的方法在Python2.2时发生了巨大的改变。本文仅描述Python2.2和更新的版本的。如果需要支持太老的Python,你还是找老文档去吧。

    2.1   基础

    Python运行时会把所有Python对象看作是PyObject*类型的指针。一个PyObject并不是一个很庞大的对象,而仅仅包含引用计数和指向 类型对象 的指针。类型对象决定了C函数从哪里调用,例如,一个属性就会查找一个对象或被其他对象使用(?)。C函数会调用 类型方法 来区别于 [].append (叫做 对象方法 )。

    所以,如果你想要定义一个新的对象类型,你需要创建类型对象。

    这个步骤可以用例子来解释,如下就是一个很小,但是完整定义的新类型。

    #include <Python.h>

    typedef struct {

        PyObject_HEAD

        //这里描述各个字段

    } noddy_NoddyObject;

     

    static PyTypeObject noddy_NoddyType={

        PyObject_HEAD_INIT(NULL)

        0,                             //ob_size

        "noddy.Noddy",                 //tp_name

        sizeof(noddy_NoddyObject),     //tp_basicsize

        0,                             //tp_itemsize

        0,                             //tp_dealloc

        0,                             //tp_print

        0,                             //tp_getattr

        0,                             //tp_setattr

        0,                             //tp_compare

        0,                             //tp_repr

        0,                             //tp_as_number

        0,                             //tp_as_sequence

        0,                             //tp_as_mapping

        0,                             //tp_hash

        0,                             //tp_call

        0,                             //tp_str

        0,                             //tp_getattro

        0,                             //tp_setattro

        0,                             //tp_as_buffer

        Py_TPFLAGS_DEFAULT,            //tp_flags

        "Noddy objects",               //tp_doc

    };

     

    static PyMethodDef noddy_methods[]={

        {NULL}

    };

     

    #ifndef PyMODINIT_FUNC

    #define PyMODINIT_FUNC void

    #endif

    PyMODINIT_FUNC initnoddy(void) {

        PyObject* m;

        noddy_NoddyType.tp_new=PyType_GenericNew;

        if (PyType_Ready(&noddy_NoddyType)<0)

            return;

        m=Py_InitModule3("noddy",noddy_methods,"Example module with new type");

        Py_INCREF(&noddy_NoddyType);

        PyModule_AddObject(m,"Noddy",(PyObject*)&noddy_NoddyType);

    }

    这里还有很多地方(bit)是空的,以后可以逐渐知道他们的意义。

    首先是:

    typedef struct {

        PyObject_HEAD

    } noddy_NoddyObject;

    这就是Noddy需要包含的,在这种情况下没办法包含Python对象以外的东西(This is what a Noddy object will contain--in this case, nothing more than every Python object contains),也就是一个类型对象的指针。这是由PyObject_HEAD宏所引进的字段。在这里使用宏是为了规范化层次并且便于调试。注意,在宏 PyObject_HEAD 后面是没有分号";"的,因为宏里面已经包含了一个了。一定要小心不注意加上的分号,因为习惯使然,你的编译器也会提示,但是其他人也许却不注意(在Windows上MSVC会把这个问题当作错误,并且不允许编译)。

    与之相比,我们看看标准Python整数的定义:

    typedef struct {

        PyObject_HEAD

        long ob_ival;

    } PyIntObject;

    继续看看类型对象:

    static PyTypeObject noddy_NoddyType={

        PyObject_HEAD_INIT(NULL)

        0,                          //ob_size

        "noddy,Noddy",              //tp_name

        sizeof(noddy_NoddyObject),  //tp_basicsize

        0,                          //tp_itemsize

        0,                          //tp_dealloc

        0,                          //tp_print

        0,                          //tp_getattr

        0,                          //tp_setattr

        0,                          //tp_compare

        0,                          //tp_repr

        0,                          //tp_as_number

        0,                          //tp_as_sequence

        0,                          //tp_as_mapping

        0,                          //tp_hash

        0,                          //tp_call

        0,                          //tp_str

        0,                          //tp_getattro

        0,                          //tp_setattro

        0,                          //tp_as_buffer

        Py_TPFLAGS_DEFAULTS,        //tp_flags

        "Noddy objects",            //tp_doc

    };

    现在你再去看看 object.h 中 PyTypeObject 的定义,你会发现有如上的很多字段定义。剩余的字段会被C编译器以0填充,按照惯例,如果你不用他们那就别碰他们。

    这对于为未来预留功能位置比较有用:

    PyObject_HEAD_INIT(NULL)

    这行有点恶心,而我们还可以这样写:

    PyObject_HEAD_INIT(&PyType_Type)

    作为一个类型对象type的类型,但是这些并不是总是会被C编译器接受。幸运的,是,这些空着的字段会自动被 PyType_Ready() 所填充。

    0,                              //ob_size

    字段ob_size并没有用上,它放在这个位置只是为了兼容旧版本的Python的扩展模块。这个字段总是设为0。

    "noddy.Noddy",                  //tp_name

    这是这个类型的名字。这将会显示在默认的文本描述或者一些错误信息中,例如:

    >>> ""+noddy.new_noddy()

    Traceback (most recent call last):

      File "<stdin>", line1, in ?

    TypeError: cannot add type "noddy.Noddy" to string

    注意名字是以点号分隔的,同时包含模块名和类型名。这里的模块名是noddy,而类型是Noddy,所以设置类型名为noddy.Noddy。

    sizeof(noddy_NoddyObject),      //tp_basicsize

    这是让Python知道在调用 PyObject_New() 时分配多少内存。

    Note

    如果你想要你的类型可以被子类化,并且你的类型作为基类时拥有相同的tp_basicsize,你可能在多继承时遇到问题。因为该子类会在 __bases__ 中列出自定义的类型,而且在调用 __new__ 失败时不会报出错误。想要避免这个问题可以设置一个足够大的 tp_basicsize ,至少比父类型要大。大多数时候这是正确的,无论你实例化对象,还是给基类对象添加数据成员,因此也会增加其大小。

    0,                              //tp_itemsize

    对于列表和字符串,这里应该是可变长度,不过暂时忽略。

    跳过我们没有定义的类型方法,我们设置类标志为 Py_TPFLAGS_DEFAULT

    Py_TPFLAGS_DEFAULT,             //tp_flags

    所有的类型都应该包含这个常量。它允许在当前Python版本中所有成员定义。

    我们提供了一个doc string到 tp_doc

    "Noddy objects",                //tp_doc

    现在我们看看类型方法。我们在模块中实现这些。一会的例子中展示。

    现在所作的都是为了创建Noddy对象。允许对象的创建,我们必须提供一个 tp_new 的实现。我们也可以使用默认的实现,也就是 PyType_GenericNew() 。只需要将其赋值到 tp_new 的位置即可。不过有些平台和C编译器实现中不允许用一个函数调用来初始化一个结构体。所以,我们将赋值放到模块初始化函数中,在 PyType_Ready() 之前:

    noddy_NoddyType.tp_new=PyType_GenericNew;

    if (PyType_Ready(&noddy_NoddyType)<0)

        return;

    所有其他类型方法都是NULL,一会在写。

    剩余的部分都很像,除了 initnoddy() 中的一些代码:

    if (PyType_Ready(&noddy_NoddyType)<0)

        return;

    这会初始化Noddy类型,填入成员,包括 ob_type

    PyModule_AddObject(m,"Noddy",(PyObject*)&noddy_NoddyType);

    这回添加类型到模块字典,这样我们就可以通过如下来创建Noddy的示例了:

    >>> import noddy

    >>> mynoddy=noddy.Noddy()

    就这些了,剩余的就是构建了,将这些代码放到 noddy.c 中,然后 setup.py 中写:

    from distutils.core import setup,Extension

    setup(name="noddy",version="1.0",

        ext_modules=[Extension("noddy",["noddy.c"])])

    然后输入:

    $ python setup.py build

    这时就会在子目录产生 noddy.so ,将其放到Python启动目录下,就可以按照上面的导入该模块并生成实例了。

    其实并不难,不过现在功能还不够有趣,没有数据,不能做任何事,也不能被继承。

    2.1.1   给例子添加数据和方法

    让我们给这个例子添加数据和方法。也要让着个类型可以作为基类。我们创建一个新的模块,noddy2添加这些功能:

    #include <Python.h>

    #include <structmember.h>

     

    typedef struct {

        PyObject_HEAD

        PyObject *first;

        PyObject *last;

        int number;

    } Noddy;

     

    static void Noddy_dealloc(Noddy *self) {

        Py_XDECREF(self->first);

        Py_XDECREF(self->last);

        self->ob_type->tp_free((PyObject*)self);

    }

     

    static PyObject *Noddy_New(PyTypeObject *type, PyObject *args, PyObject *kwds) {

        Noddy *self;

        self=(Noddy*)type->tp_alloc(type,0);

        if (self!=NULL) {

            self->first=PyString_FromString("");

            if (self->first==NULL) {

                Py_DECREF(self);

                return NULL;

            }

            self->last=PyString_FromString("");

            if (self->last==NULL) {

                Py_DECREF(self);

                return NULL;

            }

            self->number=0;

        }

        return (PyObject*)self;

    }

     

    static int Noddy_init(Noddy *self, PyObject *args, PyObject *kwds) {

        PyObject *first=NULL, *last=NULL, *tmp;

        static char *kwlist[]={"first", "last", "number", NULL};

        if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,

                &first, &last, &self->number))

            return -1;

        if (first) {

            tmp=self->first;

            Py_INCREF(first);

            self->first=first;

            Py_XDECREF(tmp);

        }

        if (last) {

            tmp=self->last;

            Py_INCREF(last);

            self->last=last;

            Py_XDECREF(tmp);

        }

        return 0;

    }

     

    static PyMemberDef Noddy_members[]={

        {"first",   T_OBJECT_EX,    offsetof(Noddy, first), 0,  "first name"},

        {"last",    T_OBJECT_EX,    offsetof(Noddy, last),  0,  "last name"},

        {"number",  T_OBJECT_EX,    offsetof(Noddy, number),0,  "number"},

        {NULL}

    };

     

    static PyObject *Noddy_name(Noddy *self) {

        static PyObject *format=NULL;

        PyObject *args, *result;

        if (format==NULL) {

            format=PyString_FromString("%s %s");

            if (format==NULL)

                return NULL;

        }

        if (self->first==NULL) {

            PyErr_SetString(PyExc_AttributeError, "first");

            return NULL;

        }

        if (self->last==NULL) {

            PyErr_SetString(PyExc_AttributeError, "last");

            return NULL;

        }

        args=Py_BuildValue("OO", self->first, self->last);

        if (args==NULL)

            return NULL;

        result=PyString_Format(format, args);

        Py_DECREF(args);

        return result;

    }

     

    static PyMethodDef Noddy_methods[] = {

        {"name",    (PyCFunction)Noddy_name,    METH_NOARGS,

                "return name, combining the first and last name"},

        {NULL}

    };

     

    static PyTypeObject NoddyType = {

        PyObject_HEAD_INIT(NULL)

        0,              //ob_size

        "noddy.Noddy",  //tp_name

        sizeof(Noddy),  //tp_basicsize

        0,              //tp_itemsize

        (destructor)Noddy_dealloc,  //tp_dealloc

        0,              //tp_print

        0,              //tp_getattr

        0,              //tp_setattr

        0,              //tp_compare

        0,              //tp_repr

        0,              //tp_as_number

        0,              //tp_as_sequence

        0,              //tp_as_mapping

        0,              //tp_hash

        0,              //tp_call

        0,              //tp_str

        0,              //tp_getattro

        0,              //tp_setattro

        0,              //tp_as_buffer

        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   //tp_flags

        "Noddy objects",    //tp_doc

        0,              //tp_traverse

        0,              //tp_clear

        0,              //tp_richcompare

        0,              //tp_weaklistoffset

        0,              //tp_iter

        0,              //tp_iternext

        Noddy_methods,  //tp_methods

        Noddy_members,  //tp_members

        0,              //tp_getset

        0,              //tp_base

        0,              //tp_dict

        0,              //tp_descr_get

        0,              //tp_descr_set

        0,              //tp_dictoffset

        (initproc)Noddy_init,   //tp_init

        0,              //tp_alloc

        Noddy_new,      //tp_new

    };

     

    static PyMethodDef module_methods[] = {

        {NULL}

    };

     

    #ifndef PyMODINIT_FUNC

    #define PyMODINIT_FUNC void

    #endif

    PyMODINIT_FUNC

    initnoddy2(void) {

        PyObjec *m;

        if (PyType_Ready(&NoddyType)<0)

            return;

        m=Py_InitModule3("noddy2", module_methods,

                Example");

        if (m==NULL)

            return;

        Py_INCREF(&NoddyType);

        PyModule_AddObject(m, "Noddy", (PyObject*)&NoddyType);

    }

    开头时导入了 structmember.h 头文件。用于处理属性,稍后解释。

    Noddy对象也缩写为Noddy了,其类型对象是 NoddyType 。

    Noddy类型有三个数据属性,first、last、number。first和last是Python字符串,number是整数。对象结构体中记录了这三个字段的实际值。

    因为有数据成员了,所以需要小心的分配和释放。至少需要释放方法(deallocation method),即 Noddy_dealloc() 函数。并且指定到 tp_dealloc 成员。负责将几个对象的引用计数减少。使用 Py_XDECREF() 因为first和last有可能是NULL。随后会调用 tp_free 成员来释放对象的内存。注意对象的类型有可能不是Noddy,有可能是其子类。

    我们需要确保first和last初始化为空字符串,在 Noddy_new() 中做。并将该函数赋值到 tp_new 成员。

    tp_new 成员用于响应创建(create, 而不是初始化)类型的对象。暴露为Python的 __new__() 方法。实现 new 方法的一个目的是所有实例值都有初始化的值。此例中,我们使用新方法来确保初始值不是NULL。如果我们不在乎初始值是NULL,则可以使用 PyType_GenericNew() 作为我们的 new 方法,也就是以前所做的。 PyType_GenericNew() 初始化所有的实例变量到NULL。

    new 方法是静态方法,传入要初始化的类型及其参数,返回新创建的对象。 new 方法总是接收可选的和关键字参数,不过通常被忽略,留下参数处理到初始化方法。注意如果类型支持子类,传递来的类型可能不是之前定义的。 new 方法调用 tp_alloc 槽来分配内存。我们并不填充 tp_alloc 槽。 PyType_Ready() 会自动填充它,集成自基类,缺省是 object 。大多数类型都是默认分配的。

    如果你创建一个合作的(co-operative) tp_new (调用基类的 tp_new 或 __new__() ),你必须不能假设调用顺序。总是静态检测你要调用的类型,并直接调用 tp_new ,或通过 type->tp_base->tp_new 。如果你不这么做,你类型的Python子类也会集成Python定义的类,而无法正常工作。你根本就无法创建子类的对象,而得到TypError。

    初始化方法 Noddy_init() 放入 tp_init 成员。暴露为Python的 __init__() 方法,用于初始化刚创建对象。不像 new 方法,我们无法确保初始化器一定被调用。初始化器在被解包时不会被调用,且可以被重载。我们的初始化器接收参数来提供初始化值。初始化器总是接受可选的和关键字参数。

    初始化器可以被多次调用。任何人可以调用 __init__() 方法。因为如此,我们必须小心的确保赋值。可能被诱惑这么干:

    if (first) {

        Py_XDECREF(self->first);

        Py_INCREF(first);

        self->first=first;

    }

    但是这是有风险的。我们的类型没有严格限制first的类型,所以其可能是其他类型的对象。有可能在方法first成员时触发其destructor。要避免这种可能,我们总是重新赋值成员,在减少其引用计数前,原因:

    1. 当我们绝对知道引用计数大于1时
    2. 当我们知道对象的deallocation不会在我们的代码里被调用时
    3. 当tp_dealloc 在GC调用时不支持减少引用计数时

    要暴露实例属性,有多种办法,最简单的办法是定义成员定义,即 Noddy_members 。然后放在 tp_members 成员。

    每个成员定义都有个成员名字,类型,偏移值,访问标识和文档字符串。

    这种方式的缺点是没法提供限制属性赋值的方法。我们期望first和last是字符串,但是任何Python对象都可以被赋值。而且这些属性也可以被删除,甚至C指针到NULL。哪怕我们已经初始化为非NULL值了,成员在被删除时会被设置为NULL。

    定义了一个方法叫 name() ,输出first和last组合的结果。

    方法实现为一个C函数,接受Noddy(或其子类)作为第一个参数。方法总是接受一个实例作为第一个参数。方法总是接受可选和关键字参数,不过此例我们不关心参数,就定义了不接受参数。有如:

    def name(self):

        return '%s %s'%(self.first, self.last)

    注意我们必须检查first和last不能是NULL。这是因为删除会导致成员变为NULL。更好的办法是检测属性值必须是String类型,下一节讨论。

    已有的定义方法也需要放到一个数组中 Noddy_methods ,然后放到 tp_methods 成员。注意我们使用 METH_NOARGS 标识来表示不接受参数。

    最后我们定义这个类型可以作为基类,通过 Py_TPFLAGS_BASETYPE 。一直到现在,很小心的确保了我们的对象可以被作为基类使用。

    2.1.2   为数据属性提供控制

    本节讨论如何给Noddy对象的属性first和last提供控制。在前面章节的模块中,实例变量first和last可以设置为非空字符串,我们想要确保这些属性总是包含字符串。

    #include <Python.h>

    #include "structmember.h"

     

    typedef struct {

        PyObject_HEAD

        PyObject *first;

        PyObject *last;

        int number;

    } Noddy;

     

    static void Noddy_dealloc(Noddy *self) {

        Py_XDECREF(self->first);

        Py_XDECREF(self->last);

        self->ob_type->tp_free((PyObject*)self);

    }

     

    static PyObject *Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {

        Noddy *self;

        self=(Noddy*)type->tp_alloc(type,0);

        if (self!=NULL) {

            self->first=PyString_FromString("");

            if (self->first==NULL) {

                Py_DECREF(self);

                return NULL;

            }

            self->last=PyString_FromString("");

            if (self->last==NULL) {

                Py_DECREF(self);

                return NULL;

            }

            self->number=0;

        }

        return (PyObject*)self;

    }

     

    static int Noddy_init(Noddy *self, PyObject *args, PyObject *kwds) {

        PyObject *first=NULL, *last=NULL, *tmp;

        static char *kwlist[]={"first","last","number", NULL};

        if (!PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,

            &first, &last, &self->number))

            return -1;

        if (first) {

            tmp=self->first;

            Py_INCREF(first);

            self->first=first;

            Py_DECREF(tmp);

        }

        if (last) {

            tmp=self->last;

            Py_INCREF(last);

            self->last=last;

            Py_DECREF(tmp);

        }

        return 0;

    }

     

    static PyMemberDef Noddy_members[]={

        {"number",  T_INT,  offsetof(Noddy,number), 0, "noddy number"},

        {NULL}

    };

     

    static PyObject *Noddy_getfirst(Noddy *self, void *closure) {

        Py_INCREF(self->first);

        return self->first;

    }

     

    static int Noddy_setfirst(Noddy *self, PyObject *value, void *closure) {

        if (value==NULL) {

            PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");

            return -1;

        }

        if (!PyString_Check(value)) {

            PyErr_SetString(PyExc_TypeError, "The first attribute value must be a string");

            return -1;

        }

        Py_DECREF(self->first);

        Py_INCREF(value);

        self->first=value;

        return 0;

    }

     

    static PyObject *Noddy_getlast(Noddy *self, void *closure) {

        Py_INCREF(self->last);

        return self->last;

    }

     

    static int Noddy_setlast(Noddy *self, PyObject *value, void *closure) {

        if (value==NULL) {

            PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");

            return -1;

        }

        if (!PyString_Check(value)) {

            PyErr_SetString(PyExc_TypeError, "The last attribute value must be a string");

            return -1;

        }

        Py_DECREF(self->last);

        Py_INCREF(value);

        self->last=value;

        return 0;

    }

     

    static PyGetSetDef Noddy_getseters[]={

        {"first",   (getter)Noddy_getfirst, (setter)Noddy_setfirst, "first name",   NULL},

        {"last",    (getter)Noddy_getlast,  (setter)Noddy_setlast,  "last name",    NULL},

        {NULL}

    };

     

    static PyObject *Noddy_name(Noddy *self) {

        static PyObject *format=NULL;

        PyObject *args, *result;

        if (format==NULL) {

            format=PyString_FromString("%s %s");

            if (format==NULL)

                return NULL;

        }

        args=Py_BuildValue("OO",self->first,self->last);

        if (args==NULL)

            return NULL;

        result=PyString_Format(format,args);

        Py_DECREF(args);

        return result;

    }

     

    static PyMethodDef Noddy_methods[]={

        {"name",    (PyCFunction)Noddy_name,    METH_NOARGS,    "return name"},

        {NULL}

    };

     

    static PyTypeObject NoddyType={

        PyObject_HEAD_INIT(NULL)

        0,                      //ob_size

        "noddy.Noddy",          //tp_name

        sizeof(Noddy),          //tp_basicsize

        0,                      //tp_itemsize

        (destructor)Noddy_dealloc,  //tp_dealloc

        0,                      //tp_print

        0,                      //tp_getattr

        0,                      //tp_setattr

        0,                      //tp_compare

        0,                      //tp_repr

        0,                      //tp_as_number

        0,                      //tp_as_sequence

        0,                      //tp_as_mapping

        0,                      //tp_hash

        0,                      //tp_call

        0,                      //tp_str

        0,                      //tp_getattro

        0,                      //tp_setattro

        0,                      //tp_as_buffer

        Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, //tp_flags

        "Noddy object",         //tp_doc

        0,                      //tp_traverse

        0,                      //tp_clear

        0,                      //tp_richcompare

        0,                      //tp_weaklistoffset

        0,                      //tp_iter

        0,                      //tp_iternext

        Noddy_methods,          //tp_methods

        Noddy_members,          //tp_members

        Noddy_getseters,        //tp_getset

        0,                      //tp_base

        0,                      //tp_dict

        0,                      //tp_descr_get

        0,                      //tp_descr_set

        0,                      //tp_dictoffset

        (initproc)Noddy_init,   //tp_init

        0,                      //tp_alloc

        Noddy_new,              //tp_new

    };

     

    static PyMethodDef module_methods[]={

        {NULL}

    };

     

    #ifndef PyMODINIT_FUNC

    #define PyMODINIT_FUNC void

    #endif

     

    PyMODINIT_FUNC initnoddy3(void) {

        PyObject *m;

        if (PyType_Ready(&Noddy_Type)<0)

            return;

        m=Py_InitModule3("noddy3",module_methods,

            "Example noddy3");

        if (m==NULL)

            return;

        Py_INCREF(&NoddyType);

        PyModule_AddObject(m, "Noddy", (PyObject*)&NoddyType);

    }

    要实现更加精细的控制,可以自己修改getter和setter函数。如下就是first属性对应的set和get函数:

    Noddy_getfirst(Noddy *self, void *closure) {

        Py_INCREF(self->first);

        return self->first;

    }

     

    static int Noddy_setfirst(Noddy *self, PyObject *value, void *closure) {

        if (value==NULL) {

            PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");

            return -1;

        }

        if (!PyString_Check(value)) {

            PyErr_SetString(PyExc_TypeError,

                    "The first attribute value must be a string");

            return -1;

        }

        Py_DECREF(self->first);

        Py_INCREF(value);

        self->first=value;

        return 0;

    }

    setter和getter函数会传入Noddy对象和一个 "closure" 。在这个例子里,closure会被忽略。closure用以支持定义数据时的高级用法。例如允许一组setter和getter函数决定closure里数据的设置和获取。

    setter函数传入Noddy对象,新的值,以及closure。新的值可以时NULL,表示该属性需要被删除。在我们的setter里,如果属性被删除或传入的值不是字符串则抛出异常。

    创建一个数组 PyGetSetDef 结构来声明:

    static PyGetSetDef Noddy_getseters[] = {

        {"first",

            (getter)Noddy_getfirst, (setter)Noddy_setfirst,

            "first name", NULL},

        {"last",

            (getter)Noddy_getlast, (setter)Noddy_setlast,

            "last name", NULL},

        {NULL}

    };

    并且注册到 tp_getset 槽:

    Noddy_getseters,        //tp_getset

    最后要提到的是closure,这里用到时都是传入NULL的。

    还可以删除成员定义:

    static PyMemberDef Noddy_members[] = {

        {"number",  T_INT, offsetof(Noddy, number), 0,

            "noddy number"},

        {NULL}

    };

    我们还需要更新 tp_init 处理器来允许传入string:

    static int

    Noddy_init(Noddy *self, PyObject *args, PyObject *kwds) {

        PyObject *first=NULL, *last=NULL, *tmp;

        static char *kwlist[] = {"first", "last", "number", NULL};

        if (!PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,

                &first, &last, &self->number))

            return -1;

        if (first) {

            tmp=self->first;

            Py_INCREF(first);

            self->first=first;

            Py_DECREF(tmp);

        }

        if (last) {

            tmp=self->last;

            Py_INCREF(last);

            self->last=last;

            Py_DECREF(tmp);

        }

        return 0;

    }

    通过这些修改,我们可以确保first和last成员不会是NULL,所以可以在所有情况中不必检查其是否为NULL了。这意味着大部分的 Py_XDECREF() 可以改为 Py_DECREF() 。唯一无法修改的地方是 dealloctor ,因为在初始化这些成员时可能会出错。

    我们也将模块初始化函数改为初始化函数,以及增加了另外的定义到 setup.py 文件。

    2.1.3   支持循环引用垃圾收集

    Python有循环垃圾收集器,可以将对象在引用计数不为零时仍然标记为无用。这可以解决循环引用。例如:

    >>> l=[]

    >>> l.append(l)

    >>> del l

    在这里例子中,列表包含了他自身。当删除时,他仍然持有他自己的引用,其引用计数不会到0。幸运的是Python的循环垃圾收集会实际指出该问题,并释放。

    在第二个版本的Noddy例子,我们允许任何类型的对象存储到first和last属性。这意味着Noddy对象可以进入循环:

    >>> import noddy2

    >>> n=noddy2.Noddy()

    >>> l=[n]

    >>> n.first=l

    这很蠢,但提醒我们需要加入循环垃圾收集器到Noddy的例子。要支持循环垃圾收集,类型需要填充2个槽并设置类标识允许这些槽。

    #include <Python.h>

    #include "structmember.h"

     

    typedef struct {

        PyObject_HEAD

        PyObject *first;

        PyObject *last;

        int number;

    } Noddy;

     

    static int

    Noddy_traverse(Noddy *self, visitproc visit, void *arg {

        int vert;

        if (self->first) {

            vret=visit(self->first,arg);

            if (vert!=0)

                return vert;

        }

        if (self->last) {

            vert=visit(self->last, arg);

            if (vert!=0)

                return vert;

        }

        return 0;

    }

     

    static int

    Noddy_clear(Noddy *self) {

        PyObject *tmp;

        tmp=self->first;

        self->first=NULL;

        Py_XDECREF(tmp);

     

        tmp=self->last;

        self->last=NULL;

        Py_XDECREF(tmp);

     

        return 0;

    }

     

    static void

    Noddy_dealloc(Noddy *self) {

        Noddy_clear(self);

        self->ob_type->tp_free((PyObject*)self);

    }

     

    static PyObject *

    Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {

        Noddy *self;

        self=(Noddy*)type->tp_alloc(type,0);

        if (self!=NULL) {

            self->first=PyString_FromString("");

            if (self->first==NULL) {

                Py_DECREF(self);

                return NULL;

            }

            self->last=PyString_FromString("");

            if (self->last==NULL) {

                Py_DECREF(self);

                return NULL;

            }

            self->number=0;

        }

        return (PyObject*)self;

    }

     

    static int

    Noddy_init(Noddy *self, PyObject *args, PyObject *kwds) {

        PyObject *first=NULL, *last=NULL, *tmp;

        static char *kwlist[] = {"first", "last", "number", NULL};

        if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,

                &first, &last, &self->number))

            return -1;

        if (first) {

            tmp=self->first;

            Py_INCREF(first);

            self->first=first;

            Py_XDECREF(tmp);

        }

        if (last) {

            tmp=self->last;

            Py_INCREF(last);

            self->last=last;

            Py_XDECREF(tmp);

        }

        return 0;

    }

     

    static PyMemberDef Noddy_members[] = {

        {"first",   T_OBJECT_EX,    offsetof(Noddy,first),  0,

            "first name"},

        {"last",    T_OBJECT_EX,    offsetof(Noddy,last),   0,

            "last name"},

        {"number",  T_INT,          offsetof(Noddy,number), 0,

            "noddy number"},

        {NULL}

    };

    @wait 代码都没抄完呢,一大堆废话

    2.1.4   继承其他类型

    @wait

    2.2   类型方法

    快速了解类型方法(type method)的实现。如下是 PyTypeObject 的定义,有些字段用于调试:

    typedef struct _typeobject {

        PyObject_VAR_HEAD

        char *tp_name;          //用于打印

        int tp_basicsize, tp_itemsize;  //用于分配

        //标准操作的实现方法

        destructor tp_dealloc;

        printfunc tp_print;

        getattrfunc tp_getattr;

        setattrfunc tp_setattr;

        cmpfunc tp_compare;

        reprfunc tp_repr;

        //标准类的方法集合

        PyNumberMethods *tp_as_number;

        PySequenceMethods *tp_as_sequence;

        PyMappingMehtods *tp_as_mapping;

        //更多标准操作(二进制兼容)

        hashfunc tp_hash;

        ternaryfunc tp_call;

        reprfunc tp_str;

        getattrfunc tp_getattro;

        setattrfunc tp_setattro;

        //访问对象IO缓冲的函数

        PyBufferProcs *tp_as_buffer;

        //定义可选和扩展操作的标识

        long tp_flags;

        char *tp_doc;       //文档字符串

        //从2.0开始的所有可访问对象调用函数

        traverseproc tp_traverse;

        //删除包含对象的引用

        inquiry tp_clear;

        //从2.1开始,富比较

        richcmpfunc tp_richcompare;

        //弱引用

        long tp_weaklistoffset;

        //从2.2开始,迭代器

        getiterfunc tp_iter;

        iternextfunc tp_iternext;

        //属性描述符和子类支持

        struct PyMethodDef *tp_methods;

        struct PyMemberDef *tp_members;

        struct PyGetSetDef *tp_getset;

        struct _typeobject *tp_base;

        PyObject *tp_dict;

        descrgetfunc tp_descr_get;

        descrsetfunc tp_descr_set;

        long tp_dictoffset;

        initproc tp_init;

        allocfunc tp_alloc;

        newfunc tp_new;

        freefunc tp_free;

        inquiry tp_is_gc;

        PyObject *tp_bases;

        PyObject *tp_mro;

        PyObject *tp_cache;

        PyObject *tp_subclasses;

        PyObject *tp_weaklist;

    } PyTypeObject;

    这里有一大堆的方法,无需担心太多,通常只需要实现一部分。

    接下来介绍各种对应的处理器。不会进入结构定义,因为这里一堆历史兼容问题,确保你的初始化按照正确的字段顺序。你可以参照该顺序,只是修改自己所需的。

    char *tp_name;  //for printing

    类型的名字,有如上节所说,会在很多地方出现,选择个有帮助的。

    int tp_basicsize, tp_itemsize;  //for allocation

    这些字段供内存分配。Python对变长对象有内置支持,需要用到 tp_itemsize 字段。

    char *tp_doc;

    存放一个字符串来供 obj.__doc__ 返回文档字符串。

    现在回到基本类型方法。

    2.2.1   销毁与释放

    destructor tp_dealloc;

    该函数在引用计数降低到0时调用。如果你的类型有需要释放的内存或其他要执行的清理,就将其放在这里。对象本身也需要在这里释放,一个例子:

    static void

    newdatatype_dealloc(newdatatypeobject *obj) {

        free(obj->obj_UnderlyingDatatypePtr);

        obj->ob_type->tp_free(obj);

    }

    需要关注的点是deallocator可能会遗留异常。当deallocator将异常放到栈中,可能无人会看到这个异常,而且这个异常未必来自于哪个deallocator。这可能导致难以调试的问题。正确的做法是将未决异常放到不安全的动作之前,并在完成时恢复。可以用 PyErr_Fetch() 和 PyErr_Restore() 函数:

    static void my_dealloc(PyObject *obj) {

        MyObject *self=(MyObject*)obj;

        PyObject *cbresult;

        if (self->my_callback!=NULL) {

            PyObject *err_type, *err_value, *err_traceback;

            int have_error=PyErr_Occurred() ? 1 : 0;

            if (have_error)

                PyErr_Fetch(&err_type, &err_value, &err_traceback);

            cbresult=PyObject_CallObject(self->my_callback, NULL);

            if (cbresult==NULL)

                PyErr_WriteUnraisable(self->my_callback);

            else

                Py_DECREF(cbresult);

            if (have_error)

                PyErr_Restore(err_type, err_value, err_trackback);

            Py_DECREF(self->my_callback);

        }

        obj->ob_type->tp_free((PyObject*)self);

    }

    2.2.2   对象表达

    在Python里有三种方法生成对象的文本表示, repr() 函数、 str() 函数和 print 语句。对大多数对象, print 语句就等同于 str() 函数,但是其实可以指定打印到特定的 FILE* ,这对于打印到文件变得靠谱。

    这三个处理器是可选的,常用类型只需要实现 tp_str 和 tp_repr 处理器即可:

    reprfunc tp_repr;

    reprfunc tp_str;

    printfunc tp_print;

    tp_repr 处理器应该返回一个字符串对象包含了实例的描述,简单的例子如:

    static PyObject *newdatatype_repr(newdatatypeobject *obj) {

        return PyString_FromFormat("Repr-ified_newdatatype{{size:\%d}}",

            obj->obj_UnderlyingDatatypePtr->size);

    }

    如果没有提供 tp_repr 处理器,解释器会提供类型的 tp_name 和一个对象的唯一值。

    tp_str 处理 str() 的请求, tp_repr 处理 repr() 的请求。这使得可以在对象上调用 str() 。实现类似于 tp_repr 函数,但是结果字符串应该是人类可读的。如果 tp_str 没有提供,就使用 tp_repr 处理器。

    如下简单例子:

    static PyObject *newdatatype_str(newdatatypeobject *obj) {

        return PyString_FromFormat("Stringified_newdatatype{{size:\%d}}",

            obj->obj_UnderlyingDatatypePtr->size);

    }

    而print功能会在需要打印实例时调用。例如,如果结点是TreeNode类型,则打印功能调用就是:

    print node

    还有个flags参数,只有个 Py_PRINT_RAW ,会假设无需字符串转义就打印。

    打印函数接受一个文件对象作为第一个参数,你可以这样写数据到文件。

    如下是例子:

    static int

    newdatatype_print(newdatatypeobject *obj, FILE *fp, int flags) {

        if (flags & Py_PRINT_RAW) {

            fprintf(fp, "<{newdatatype object--size: %d}>",

                    obj->obj_UnderlyingDatatypePtr->size);

        } else {

            fprintf(fp, "\"<{newdatatype object--size: %d}>\"",

                    obj->obj_UnderlyingDatatypePtr->size);

        }

        return 0;

    }

    2.2.3   属性管理

    对每个需要支持属性的对象,其类型必须提供函数来控制属性如何被解析。这需要一个函数来获取属性,另一个用来设置属性。删除一个属性时特例,对应的新值是NULL。

    Python支持两对属性处理器,一个类型支持属性成对处理。区别是一对接受属性名字为 char* ,而另一种接受 PyObject* 。每种类型都能实现更多便利:

    getattrfunc tp_getattr;

    setattrfunc tp_setattr;

    /* ... */

    getattrofunc    tp_getattrofunc;

    setattrofunc    tp_setattrofunc;

    如果访问属性总是简单操作,有个通用的实现可以用于提供 PyObject* 版本的属性管理。实际需要一个类型相关的属性处理器来实现,自Python2.2消失。因此有很多例子无需更新使用新的通用机制。

    2.2.4   对象比较

    @wait

    2.2.5   抽象协议支持

    Python支持一系列抽象协议,供Python C API的抽象对象层API来调用。

    一些抽象接口是在Python早期定义的。数字、映射、序列协议已经成为Python的一部分。其他协议加入则较晚。每种协议需要实现多个接口,旧协议作为类型对象的可选块。新协议会添加槽到主类型对象,一个标志位指定这些槽是否应该被解释器检查。标志位不应该设置为NULL,而应该用于表示槽,而槽可以是未填充的。

    PyNumberMethods     tp_as_number;

    PySequenceMethods   tp_as_sequence;

    PyMappingMethods    tp_as_mapping;

    如果你希望你的对象表现的像一个数字、序列、映射,你应该将实现的结构体地址填充上。可以到Python源码包里找到例子。

    hashfunc    tp_hash;

    这个函数可选提供,会返回你对象类型的哈希数字。如下是一个例子:

    static long

    newdatatype_hash(newdatatypeobject *obj) {

        long result;

        result=obj->obj_UnderlyingDatatypePtr->size;

        result=result*3;

        return result;

    }

     

    ternaryfunc tp_call;

    这个函数会被数据类型实例调用。例如obj1是你的数据类型的实例,而语句 obj1('hello') 就会调用 tp_call 。

    函数接受3个参数:

    1. arg1是数据类型实例,上例为 obj1
    2. arg2是参数元组,通过 PyArg_ParseTuple() 来解析
    3. arg3是关键字参数字典,通过 PyArg_ParseTupleAndKeywords() 来解析,不想支持就抛出 TypeError

    如下例子实现了call函数:

    static PyObject *

    newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject kwds) {

        PyObject *result;

        char *arg1;

        char *arg2;

        char *arg3;

        if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {

            return NULL;

        }

        result=PyString_FromFormat(

            "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",

            obj->obj_UnderlyingDatatypePtr->size,

            arg1, arg2, arg3);

        printf("\%s", PyString_AS_STRING(result));

        return result;

    }

    其他字段...。

    getierfunc      tp_iter;

    iternextfunc    tp_iternext;

    这些函数提供了迭代器支持。任何对象想要支持迭代其内容,都应该实现 tp_iter 和 tp_iternext 处理器。两个处理器都接受一个参数,就是对象的实例,返回新的引用。如果发生错误,应该设置异常并返回NULL。

    对于迭代器容器, tp_iter 处理器应该返回一个迭代器对象。迭代器对象用于管理迭代器的状态。容器可以支持多个迭代器,互相之间不影响,每次返回个新的迭代器即可。对象应该确保被迭代一次,实现者应该返回其本身新的引用,以及实现 tp_iternext 处理器。文件对象就是迭代器的例子。

    tp_iternext 处理器返回下一个对象的新的引用。如果迭代器已经到达末尾,就应该返回NULL而不设置异常,或 StopIteration 异常,避免异常影响性能。如果发生了错误,就应该设置异常并返回NULL。

    2.2.6   弱引用支持

    @wait

    2.2.7   更多建议

    大部分函数都可以不提供,写为0即可。类型定义必须提供,在 object.h 中。

    想要学习如何实现特定方法,就去解压Python源码包,进入 Objects 目录,搜索C源码 tp_ 前缀的即可。

    当你想要验证一个对象是否实现了,可以使用 PyObject_TypeCheck() 函数,一个样例如下:

    if (!PyObject_TypeCheck(some_object, &MyType)) {

        PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");

        return NULL;

    }

    3   使用distutils构建扩展

    自从Python1.4开始就提供一个自动构建动态载入扩展模块和自定义解释器的make文件了。自动2.0开始,这个机制不再被支持。构建自定义解释器很少用到,而构建扩展模块则更多的使用了distutils。

    使用distutils构建扩展模块需要的distutils已经是Python2.x中的标准组成。distutils也同时支持二进制包,而用户并不一定需要一个编译器来安装扩展模块。

    一个distutils包包含一个驱动脚本 setup.py 。这个纯Python文件一般形式如下:

    from distutils.core import setup,Extension

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

    setup(name='PackageName',

            version='1.0',

            description='This is a demo package',

            ext_modules=[module1])

    包含一个setup.py和一个demo.c,运行:

    python setup.py build

    将会编译demo.c,并会在build目录下产生一个"demo"扩展模块。依赖于系统,模块会在一个子目录build/lib.system中,并把名字叫做demo.so或demo.pyd。

    在setup.py,所有执行操作依靠 setup() 函数的调用。接受一大堆关键字参数,上面的例子只是使用了一些子集。一般来说,要指明包的内容。还要指明包含附加模块,有如Python源码模块,文档,子包等。参考distutils了解更多。本章只是描述构建扩展模块的基础而已。

    最好把需要填入 setup() 的参数先计算好再填入,有如上面的 ext_modules 参数就是如此。例子中,这个实例定义了一个叫做"demo"的扩展模块。

    在很多情况下,构建扩展模块比较复杂,包含很多预处理指令和链接库。下面示范了一下:

    from distutils.core import setup,Extension

    module1=Extension('demo',

            define_macros=[('MAJOR_VERSION','1'),

                           ('MINOR_VERSION','0'),

                          ]

            include_dirs=['/usr/local/lib',],

            libraries=['tcl83'],

            library_dirs=['/usr/local/lib',],

            sources=['demo.c',])

    setup(name='PackageName',

          version='1.0',

          description='This is a demo package',

          author='Martin v. Loewis',

          author_email='martin@v.loewis.de',

          url='http://www.python.org/doc/current/ext/building.html',

          long_description='long description',

          ext_modules=[module1])

    这个例子里面,setup附带了很多参数,指定了预处理定义,各类文件夹等。依赖于编译器,distutils用不同的方法传递信息。例如,在UNIX上,可能的结果如下:

    gcc -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -fPIC -DMAJOR_VERSION=1 \

        -DMINOR_VERSION=0 -I/usr/local/include -I/usr/local/include/python2.2 \

        -c demo.c -o build/temp.linux-i686-2.2/demo.o

    gcc -shared build/temp.linux-i686-2.2/demo.o -L/usr/local/lib -ltcl83 \

        -o build/lib.linux-i686-2.2/demo.so

    当然,这些行只是用于演示,你应该相信distutils会做出正确的判断。

    3.1   发布你的扩展模块

    扩展模块构建完成之后,你可以用三种方法使用。

    最终用户可以直接安装模块,如:

    python setup.py install

    模块发行人员可以发布源码包:

    python setup.py sdist

    在某些情况下,附加文件需要加入到源码包里面,这通过 MANIFEST.in 文件实现,查看distutils的文档。

    如果源码发行版构建成功,模块发行人员还可以创建二进制发行版,依赖于具体平台,可以用如下:

    python setup.py bdist_wininst

    python setup.py bdist_rpm

    python setup.py bdist_dumb

    4   在Windows构建扩展

    本章解释如何在Windows下使用VC++创建Python扩展,并给出一些背景资料解释如何运行。这些知识对大家都有用。

    模块作者一般推荐使用distutils来构造扩展模块,而不是本章所描述的。但是你仍然需要有个C编译器,这里就是VC++。

    Note

    本章提到的一些文件名可能包含Python版本号。这些文件名带有2位数字的版本号,分别为主版本号和次版本号。。例如Python2.2.1的这2个数字就是22。

    4.1   照着菜谱走猫步(a cookbook approach)

    在Windows上编译扩展有两种方式,有如Unix上一样:用distutils或者手动。使用distutils方式对大多数模块工作的很好,可以参考distutils的手册。本节仅介绍手动编译C/C++编写的Python扩展模块。

    要构建扩展模块,你首先要拥有与你版本相同的Python源码,你还需要一个VC++,工程文件是供VC++ 7.1使用的,但是你也可以使用老版本的VC++。注意的是,你必须使用编译Python相同版本的VC++来编译扩展模块。例子文件 PC\example_nt\ 描述了一个例子。

    1. 复制例子文件
      把那个文件夹拷出来,以防玷污了那个文件夹。
    2. 打开工程
      在VC++中打开工程文件example.sln。
    3. 构建例子的DLL
      为了检查是否OK了,可以尝试先构建一下:
      1. 选择一个配置文件,Release或Debug,默认为Debug。
      2. 构建DLL,就是Build一下。

    4. 测试debug模式的DLL
      得到了调试构建以后,就可以在DLL所在的文件夹下打开Python解释器:
      C>..\..\PCbuild\python_d
    5. Adding parser accelerators ...
    6. Done.
    7. Python 2.2 (#28, Dec 19 2001, 23:26:37) [MSC 32 bit (Intel)] on win32
    8. Type "copyright", "credits" or "license" for more information.
    9. >>> import example
    10. [4897 refs]
    11. >>> example.foo()
    12. Hello, world
    13. [4903 refs]
    14. >>>

    15. 恭喜,你已经成功的构建了这个扩展模块。
    16. 创建自己的工程
      创建一个目录,然后把这些源文件拷进去。文件名并不一定需要与模块名有什么必然联系,但是模块的初始化函数必须与模块名相关-你可以导入
      spam 依赖于初始化函数名为 initspam() ,并且他会调用 Py_InitModule() 函数,并传递 spam 这个名字作为首参数。只是按照习俗,它一般是在spam.c或者spammodule.c中。输出文件可以叫做 spam.dll 或 spam.pyd (后一种.pyd方式是为了防止与系统DLL文件重名导致混乱)。调试模式的文件名为 spam_d.dll 或 spam_d.pyd 。
      现在你可以拷贝原工程文件后修改或重新创建一个新的工程。
      同时还要拷贝
      example_nt\example.def 到 spam\spam.def ,然后修改 spam.def 的第二行包含 initspam 。如果创建了新工程则需要自己添加 spam.def 文件,别怕这个文件就2行。另一个方法是根本不去创建.def文件,而是添加 /export:initspam 到连接器选项,可以手动修改工程配置。
    17. 创建全新工程
      创建一个VC++工作空间,选择VC++ -> Win32 -> Win32 Project,然后选择工程名和存放路径,确保存放路径与刚才的那个路径相同,在Python源码树中,就是在PC目录下,并且有相同的Include路径设置。选择Win32平台,然后OK。
      你现在就可以创建
      spam.def 了,然后添加源文件,添加进来 spam.c 和 spam.def 然后就是OK了。
      打开工程选项。确保每个需要定制的位置都修改好。需啊则C/C++这个TAB页面,选择通用范畴的弹出菜单,输入如下附加的Include路径:
      ..\Include,..\PC

    18. 然后修改通用范畴的连接器TAB页面,然后输入:
      ..\PCbuild

    19. 另外还需要一些其他设置。在Release配置页的连接TAB页,选择输入框,然后添加 pythonXX.lib 到附加依赖框(Additional Dependencies)。
      选择Debug配置页的也同样添加
      pythonXY_d.lib 文件到附加依赖。然后选择C/C++的TAB页,选择代码生成,选择"Multi-threaded Debug DLL"选项作为运行时。
      选择Release页面同样选择多线程DLL运行时。

    如果你的模块创建了新的类型,你可能在这一行碰钉子:

    PyObject_HEAD_INIT(&PyType_Type)

    需要改成:

    PyObject_HEAD_INIT(NULL)

    并且添加如下到模块初始化函数中:

    MyObject_Type.ob_type=&PyType_Type;

    参考Python FAQ的第三节了解更多。

    4.2   Unix与Windows的不同

    Unix与Windows在载入运行时代码方面完全不同。

    在Unix,动态载入的代码包含在共享对象文件(.so)中。当这个文件载入时,程序会自动在内存中重新定位函数和数据的位置。这是基本的动态连接操作。

    在Windows,动态载入代码包含在动态链接库当中(.dll)。获取这些函数的地址需要一个查找表,所以DLL代码的指针并不需要运行时重定位;而是在代码表中填入已经重定位的函数和数据地址。

    在Unix中,只有一种链接库(.a),包含一些目标文件(.o)。当链接用于创建共享对象文件(.so)时,连接器会查找标识符的位置。然后会在最终的.so文件包含所有标识符的定义。

    在Windows,有两种库。静态库和导入库(尽管都叫做.lib)。静态库有如Unix的.a文件,包含重要代码。导入库仅仅使用连接器提供的标识符,也就是加载时提供的查找表。所以连接器使用导入库构建查找表。

    如果你想构建两种动态载入模块,B和C,并可能共享代码块A。在Unix你需要传递A.a到B.so和C.so的连接过程,这会导致两次包含,B和C都包含他们自己对A的拷贝。在Windows,构造一个A.dll同时会构造一个A.lib。你可以传递A.lib到连接器B和C。A.lib并不包含代码,仅包含A代码的运行时查找表。

    在Windows,使用导入库有如 import spam ,给出要导入的名字,但是并不创建一个单独的拷贝。在Unix,连接一个静态库更像是 from spam import * ,他包含单独的拷贝。

    4.3   实践中使用DLL

    Windows Python基于VC++构建,使用其他编译器可能不会工作,下面都是基于VC++讲解。

    当在Windows下创建DLL时,你必须传递pythonXY.lib到连接器。来构建两个DLL,spam和ni(在spam中使用的C函数),你可以使用如下命令:

    cl /LD /I/python/include spam.c ../libs/pythonXY.lib

    cl /LD /I/python/include ni.c spam.lb ../libs/pythonXY.lib

    第一个命令生成3个文件 spam.obj 、 spam.dll 、 spam.lib 。其中 spam.dll 并不包含Python函数(比如 PyArg_ParseTuple() ),但是它依赖 pythonXY.lib 知道如何去找到这些Python代码。

    第二个命令创建了 ni.dll (和附带的.obj和.lib),他知道如何找到spam的函数定义,并且可以找到Python的执行。

    并不是所有的标识符都会导出到查找表。如果你想要Python看到你的标识符,就需要以 _declspec(dllexport) 来前缀声明,比如 void _declspec(dllexport) initspam(void) 或 PyObject _declspec(dllexport) *NiGetSpamData(void) 。

    VC++会导入一大堆你并不需要的库,增加超过100KB的文件尺寸,想要扔掉这些导入,打开工程属性对话框的Link页面,忽略缺省导入库。添加必须的 msvcrtxx.lib 到库列表就可以了。

    5   嵌入Python到其他应用

    前面的章节讨论如何扩展Python,如何生成适合的C库等。不过还有另一种情况:通过将Python嵌入C/C++应用以扩展程序的功能。Python嵌入实现了一些使用Python更合适的功能。这可以有很多用途,一个例子是允许用户裁减需要的Python功能。也可以用于默写使用Python编写更加方便的功能。

    嵌入Python与扩展很像。扩展Python时,主程序是Python解释器,但是嵌入Python则主程序并不是Python的-是程序的其他部分调用Python来实现一些功能。

    所以,如果要嵌入Python,你可以提供自己的主程序,这个主程序需要初始化Python解释器。至少需要调用函数 Py_Initialize() (对于MacOS,调用 PyMac_Initialize())。可以选择是否传入命令行参数到Python。然后你就可以在应用的任何地方调用Python解释器了。

    有几种方法调用解释器:可以传递一个包含Python语句的字符串到 PyRun_SimpleString() ,也可以传递一个stdio文件指针和一个文件名(用于识别错误信息)到 PyRun_SimpleFile() 。你也可以调用前几章介绍的底层操作直接控制Python对象。

    可以在目录 Demo/embed/ 中找到嵌入Python的例子。

    5.1   高层次嵌入

    嵌入Python最简单的形式是使用高层次的接口。这个接口专门用于执行Python脚本,而不需要与应用程序直接交互。例子可以在一个文件中展示:

    #include <Python.h>

    int

    main(int argc, char* argv[]) {

        Py_Initialize();

        PyRun_SimpleString("from time import time,ctime\n"

                "print 'Today is',ctime(time())\n");

        Py_Finalize();

        return 0;

    }

    如上代码首先使用 Py_Initialize() 初始化Python解释器,随后执行硬编码中的Python脚本来打印日期和时间。最后 Py_Finalize() 关闭了解释器。在真实应用中,你可能希望从其他方式获取Python脚本,文件、编辑器、数据库等。从文件获取的方式更适合使用 PyRun_SimpleFile() 函数,可以省去分配内存空间和载入文件的麻烦。

    5.2   超越高层嵌入:预览

    高层次的接口可以方便的执行Python代码,但是交换数据就很麻烦。如果需要,你可以使用低层次的接口调用。虽然多写一些C代码,但是却可以完成很多功能。

    仍然要提醒的是,Python的扩展与嵌入其实很像,尽管目的不同。前几章讨论的大多数问题在这里也同样适用。可以参考用C扩展Python时一些步骤:

    1. 转换Python类型到C类型
    2. 传递参数并调用C函数
    3. 转换返回值到Python

    当嵌入Python时,接口需要做:

    1. 转换C数据到Python
    2. 调用Python接口程序来调用Python函数
    3. 转化返回值到C

    有如你所见,数据转换的步骤用于跨语言的数据交换。唯一的不同是两次数据转换之间调用的函数。当扩展时,你调用C函数,当嵌入时,调用Python函数。

    这一章不会讨论Python和C之间的数据转换。并且假设你会使用手册来处理错误,自此只会讨论与扩展解释器不同的部分,你可以到前面的章节找到需要的信息。

    5.3   纯扩展

    第一个程序是执行一段Python脚本中的函数。有如高层接口一节,Python解释器并不会自动与程序结合。

    运行一段Python脚本中的函数的代码如下:

    #include <Python.h>

     

    int

    main(int argc, char *argv[])

    {

        PyObject *pName, *pModule, *pDict, *pFunc;

        PyObject *pArgs, *pValue;

        int i;

     

        if (argc < 3) {

            fprintf(stderr,"Usage: call pythonfile funcname [args]\n");

            return 1;

        }

     

        Py_Initialize();

        pName = PyString_FromString(argv[1]);

        /* Error checking of pName left out */

     

        pModule = PyImport_Import(pName);

        Py_DECREF(pName);

     

        if (pModule != NULL) {

            pFunc = PyObject_GetAttrString(pModule, argv[2]);

            /* pFunc is a new reference */

     

            if (pFunc && PyCallable_Check(pFunc)) {

                pArgs = PyTuple_New(argc - 3);

                for (i = 0; i < argc - 3; ++i) {

                    pValue = PyInt_FromLong(atoi(argv[i + 3]));

                    if (!pValue) {

                        Py_DECREF(pArgs);

                        Py_DECREF(pModule);

                        fprintf(stderr, "Cannot convert argument\n");

                        return 1;

                    }

                    /* pValue reference stolen here: */

                    PyTuple_SetItem(pArgs, i, pValue);

                }

                pValue = PyObject_CallObject(pFunc, pArgs);

                Py_DECREF(pArgs);

                if (pValue != NULL) {

                    printf("Result of call: %ld\n", PyInt_AsLong(pValue));

                    Py_DECREF(pValue);

                }

                else {

                    Py_DECREF(pFunc);

                    Py_DECREF(pModule);

                    PyErr_Print();

                    fprintf(stderr,"Call failed\n");

                    return 1;

                }

            }

            else {

                if (PyErr_Occurred())

                    PyErr_Print();

                fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);

            }

            Py_XDECREF(pFunc);

            Py_DECREF(pModule);

        }

        else {

            PyErr_Print();

            fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);

            return 1;

        }

        Py_Finalize();

        return 0;

    }

    这段代码从argv[1]中载入Python脚本,并且调用argv[2]中的函数,整数型的参数则是从argv数组后面得来的。如果编译和链接这个程序,执行如下脚本:

    def multiply(a,b):

        print "Will compute",a,"times",b

        c=0

        for i in range(0,a)

            c=c+b

        return c

    结果将是:

    $ call multiply multiply 3 2

    Will compute 3 times 2

    Result of call: 6

    虽然这个程序的代码挺多的,但是大部分其实都是做数据转换和错误报告。主要关于嵌入Python的开始于:

    Py_Initialize();

    pName=PyString_FromString(argv[1]);

    /* Error checking of pName left out */

    pModule=PyImport_Import(pName);

    初始化解释器之后,使用 PyImport_Import() 导入模块。这个函数需要字符串作为参数,使用 PyString_FromString() 来构造:

    pFunc=PyObject_GetAttrString(pModule,argv[2]);

    /* pFunc is a new reference */

    if (pFunc && PyCallable_Check(pFunc)) {

        ...

    }

    Py_XDECREF(pFunc);

    载入了模块以后,就可以通过 PyObject_GetAttrString() 来获取对象。如果名字存在并且可以执行则可以安全的调用它。程序随后构造参数元组,然后执行调用:

    pValue=PyObject_CallObject(pFunc,pArgs);

    函数调用之后,pValue要么是NULL,要么是返回值的对象引用。注意在检查完返回值之后要释放引用。

    5.4   扩展嵌入的Python

    至今为止,嵌入的Python解释器还不能访问应用程序本身的功能。Python的API允许扩展嵌入的Python的解释器。所以,Python可以获得其所嵌入的程序的功能。这听起来挺麻烦的,其实并不是那样。只要简单的忘记是应用程序启动了Python解释器。

    可以把程序看作一对功能的集合,可以写一些胶水代码来来让Python访问这些功能,有如你在写一个普通的Python扩展一样。例如:

    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}

    };

    添加上面的代码到 main() 函数。同样,插入如下两个语句到 Py_Initialize() 函数之后:

    numargs=argc;

    Py_InitModule("emb",EmbMethods);

    这两行代码初始化numargs变量,并且使得 emb.numargs() 函数更加易于被Python嵌入的解释器所理解。通过这个扩展,Python脚本可以做如下事情:

    import emb

    print "Number of arguments",emb.numargs()

    在实际的应用程序中,方法需要导出API以供Python使用。

    5.5   在C++中嵌入Python

    有时候需要将Python嵌入到C++程序中,而你必须有一些要注意的C++系统的细节,一般来说你要为这个程序写一个main()函数,然后使用C++编译器来编译和链接程序。而这里不需要因为使用C++而重新编译Python本身。

    5.6   链接必备条件

    当 configure 脚本执行时,可以正确的生成动态链接库使用的导出符号,而这些却不会自动被嵌入的静态链接的Python所继承,至少是在Unix。这是用于静态链接运行库(libpython.a)并且需要载入动态扩展(.so)的方式。

    问题是一些入口点是使用Python运行时定义的而仅供扩展模块使用。如果嵌入应用不使用任何这些入口点,一些链接器不会包含这些实体到最终可执行文件的符号表。一些附加的选项可以用于告知连接器不要删除这些符号。

    对于不同的平台,想要正确的检测该使用何种参数是非常困难的,但是幸运的是Python配置好了这些值。只要通过已经安装的Python解释器,启动交互解释器然后执行如下会话即可:

    >>> import distutils.sysconfig

    >>> distutils.sysconfig.get_config_var('LINKFORSHARED')

    '-Xlinker -export-dynamic'

    字符串的内容就是生成的选项。如果字符串为空,则不需要任何的附加选项。LINKFORSHARED的定义与Python顶层Makefile中的同名变量相同。



    已有 0 人发表留言,猛击->>这里<<-参与讨论


    ITeye推荐
    • —软件人才免语言低担保 赴美带薪读研!—





沪ICP备19023445号-2号
友情链接