楔子

到目前为止,我们已经介绍了很多关于解释器方面的内容,本来接下来应该要说内存管理的,但是个人觉得应该对前面的系列做一个总结。而最好的方式,就是使用Python/C API编写扩展模块,个人是这么认为的。我们编写过不少Python的模块,但显然用的都是Python语言,那么问题来了,我们如何使用C语言来编写呢?

使用C为Python编写扩展的话,是需要遵循一定套路的,而这个套路很固定。那么下面我们就来用Python写一个模块,看看这个模块如何使用C语言来实现。

关于扩展模块,这里不得不提一下Cython,使用Python/C API编写扩展是一件不轻松的事情,其实还是C语言本身比较底层吧。而Cython则是帮我们解决了这一点,Cython代码和Python高度相似,而cython编译器会自动帮助我们将Cython代码翻译成C代码,再编译成扩展模块,所以Cython本质上也是使用了Python/C API,只不过它让我们不需要直接面对C,只要我们编写Cython代码即可,会自动帮我们转成C的代码。

所以随着Cython的出现,现在使用Python/C API编写扩展的话算是越来越少了,不过话虽如此,使用Python/C API编写可以极大的帮助我们熟悉Python的底层。

那么废话不多说,直接开始吧。

编写扩展模块的基本骨架

我们先用Python编写一个模块,模块名就叫fubuki吧,也就是创建一个fubuki.py。

def f1():

"""return an integer"""

return 123

def f2(a):

"""return a + 123"""

return a + 123

非常简单的一个模块,里面只有两个简单的函数,但是我们知道当被导入时它就是一个PyModuleObject对象,里面除了我们定义的两个函数之外还有其它的属性,显然这是Python解释器在背后帮助我们完成的。

那么我们如何使用C来进行编写呢?注意接下来,我的这波操作。

/*

编写Python扩展模块,需要引入Python.h这个头文件

这个头文件,在Python安装目录的include目录下,编译的时候会自动寻找

当然这个头文件里面还导入了很多其它的头文件,我们也可以直接拿来用

*/

#include "Python.h"

/*

编写我们之前的两个函数f1和f2,必须返回PyObject *

函数里面至少要接收一个PyObject *self,而这个参数我们是不需要管的,当然不叫self也是可以的

显然跟方面方法里面的self是一个道理,所以对于Python调用者而言,f1是一个不需要接收参数的函数

*/

static PyObject *

f1(PyObject *self)

{

return PyLong_FromLong(123);

}

static PyObject *

f2(PyObject *self, PyObject *a, PyObject *b)

{

long _a, _b;

long result;

_a = PyLong_AsLong(a);

_b = PyLong_AsLong(b);

result = _a + _b;

return PyLong_FromLong(result);

}

/*

定义一个结构体数组,结构体类型为PyMethodDef

PyMethodDef里面有四个成员,分别是:函数名、函数指针(需要转成PyCFunction)、函数参数标识、函数的doc

关于PyMethodDef我们后面会单独说

*/

static PyMethodDef fubuki_function[] = {

{

"f1",

(PyCFunction)f1,

METH_NOARGS, //后面说

"this is a function named f1"

},

{

"f2",

(PyCFunction)f2,

METH_O, //后面说

"this is a function named f2"

},

//结尾要有一个{NULL, NULL, 0, NULL} 充当哨兵

{NULL, NULL, 0, NULL}

};

/*

我们编写的py文件,解释器会自动把它变成一个模块,但是这里我们需要手动定义

定义一个PyModuleDef类型结构体

*/

static PyModuleDef fubuki_module = {

PyModuleDef_HEAD_INIT, //头部信息,Python.h中提供了这个宏

"fubuki", //模块名

"this a module named fubuki", //模块的doc,没有的话直接写成NULL即可

-1, //模块的空间,这个不需要关心,直接写成-1即可

fubuki_function, //上面的那么PyMethodDef结构数组,必须写在这里,不然我们没法使用定义的函数

//下面直接写4个NULL即可

NULL, NULL, NULL, NULL

};

//到目前为止,前置工作就做完了,下面还差两步

/*

扩展库入口函数,这是一个宏,Python的源代码我们知道是使用C来编写的

但是编译的时候为了支持C++的编译器也能编译,于是需要通过extern "C"定义函数

然后这样C++编译器在编译的的时候就会按照C的标准来编译函数,这个宏就是干这件事情的,主要和python中的函数保持一致

*/

PyMODINIT_FUNC

/*

模块初始化入口,注意:模块名叫fubuki,那么下面就必须要写成PyInit_fubuki

*/

PyInit_fubuki(void)

{

// 将使用PyModuleDef定义的模块对象的指针传递进去,然后返回得到Python中的模块

return PyModule_Create(&fubuki_module);

}

整体逻辑还是非常简单的,过程如下:

include "Python.h",这个是必须的

定义我们函数,具体定义什么函数、里面写什么代码完全取决于你的业务

定义一个PyMethodDef结构体数组

定义一个PyModuleDef结构体

定义模块初始化入口,然后返回模块对象

那么如何将这个C文件变成扩展模块,显然要经过编译。

from distutils.core import *

setup(

# 打包之后会有一个egg_info,表示该模块的元信息信息,name就表示打包之后的egg文件名

# 显然和模块名是一致的

name="fubiki",

version="1.11", # 版本号

author="古明地盆",

author_email="66666@东方地灵殿.com",

# 关键来了,这里面接收一个类Extension,类里面传入两个参数

# 第一个参数是我们的模块名,必须和PyInit_xxx中的xxx保持一致,否则报错

# 第二个参数是一个列表,表示用到了哪些C文件,因为扩展模块对应的C文件不一定只有一个,我们这里是fubuki.c

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

)

当前的py文件名叫做1.py,我们在控制台中直接输入python 1.py install即可。

我们看到对应的pyd已经生成了,自动帮我们拷贝到site-packages目录中了。

我们看到了一个fubuki.pyd文件,至于中间的部分就是解释器版本,然后上面的egg-info,指的就是模块fubuki的元信息,我们打开看看。

有几个我们没有写,所以是UNKNOW,当然这都不重要,重要的是我们能不能调用了。试一试吧,当然要先把之前写的fubuki.py删掉。

import fubuki

print(fubuki.f1()) # 123

print(fubuki.f2(123)) # 246

最后再看一个神奇的东西,我们知道在pycharm这样的智能编辑器中,通过Ctrl加左键可以调到指定模块的指定位置。

神奇的一幕出现了,我们点击进去居然还能跳转,其实我们在编译成扩展模块移动到site-packages之后,pycharm就自动生成了。我们看到模块注释、函数的注释跟我们在C文件中指定的一样。

函数参数(位置参数)

编写扩展模块的基本骨架按照上面说的那样子做就可以,不过我们还遗留了一个问题,那就是参数。我们看到上面必须定义一个PyMethodDef结构体数组,那么这个PyMethodDef是什么呢?我们之前说过的。

struct PyMethodDef {

/* 内置的函数或者方法名 */

const char *ml_name;

/* 实现对应逻辑的C函数,但是需要转成PyCFunction类型,主要是为了更好的处理关键字参数 */

PyCFunction ml_meth;

/* 参数类型

#define METH_VARARGS 0x0001 扩展位置参数

#define METH_KEYWORDS 0x0002 扩展关键字参数

#define METH_NOARGS 0x0004 不需要参数

#define METH_O 0x0008 需要一个参数

#define METH_CLASS 0x0010 被classmethod装饰

#define METH_STATIC 0x0020 被staticmethod装饰

*/

int ml_flags;

//函数的__dic__

const char *ml_doc;

};

typedef struct PyMethodDef PyMethodDef;

如果不需要参数,那么ml_flags传入一个METH_NOARGS,接收一个参数传入METHOD_O即可,所以我们上面的f1对应的ml_flags是METHOD_NOARGS,f2对应的ml_flags是METHOD_O。

如果是多个参数,那么直接写成METHOD_VARAGRS即可,也就是通过扩展位置参数的方式,但是这要如何解析呢?比如:有一个函数f3接收3个参数,这在C中要如何实现呢?

static PyObject *

f3(PyObject *self, PyObject *args)

{

//目前我们定义了一个PyObject *args,如果是METH_O,那么这个args就是对应的一个参数

//如果METH_VARAGRS,还是只需要定义一个*args即可,只不过此时的*args是一个PyTupleObject,我们需要将多个参数解析出来

//假设此时我们这个函数是接收3个int,然后相加

int a, b, c;

/*

下面我们需要使用PyArg_ParseTuple进行解析,因为我们接收三个参数

这个函数返回一个整型,如果失败会返回0,成功返回非0

*/

if (!PyArg_ParseTuple(args, "iii", &a, &b)){

//失败返回NULL,后面我们会介绍如何在底层返回一个异常

return NULL;

}

return PyLong_FromLong(a + b + c);

}

所以重点就在PyArg_ParseTuple上面,这个函数的原型就是:int PyArg_ParseTuple(PyObject *args, const char *format, ...),我们注意到format,显然类似于printf,里面肯定是一些占位符,那么都支持哪些占位符呢?

i:接收一个Python中的int,然后解析成C的int

l:接收一个Python中的int,然后将传来的值解析成C的long

f:接收一个Python中的float,然后将传来的值解析成C的float

d:接收一个Python中的float,然后将传来的值解析成C的double

s:接收一个Python中的str,然后将传来的值解析成C的char *

u:接收一个Python中的str,然后将传来的值解析成C的wchar_t *

O:接收一个Python中的object,然后将传来的值解析成C的PyObject *

占位符比较多,但是我们暂时只需要掌握上面七个即可,下面演示一下。

#include "Python.h"

static PyObject *

f(PyObject *self, PyObject *args)

{

int a;

long b;

float c;

double d; //关于object和字符串我们暂时先不试,留在后面,目前只需要知道这个函数怎么用即可

if (!PyArg_ParseTuple(args, "ilfd", &a, &b, &c, &d)){

return NULL;

}

printf("int: %d, long: %d, float: %f, double: %lf\n", a, b, c, d);

return Py_None; //Py_None就是Python中的None

}

static PyMethodDef fubuki_function[] = {

{

"f",

(PyCFunction)f,

METH_VARARGS, //这里记得一定要改成METH_VARARGS,表示接收多个参数

"this is a function named f"

},

{NULL, NULL, 0, NULL}

};

static PyModuleDef fubuki_module = {

PyModuleDef_HEAD_INIT,

"fubuki",

"this a module named fubuki",

-1,

fubuki_function,

NULL, NULL, NULL, NULL

};

PyMODINIT_FUNC

PyInit_fubuki(void)

{

return PyModule_Create(&fubuki_module);

}

然后编译成扩展模块,执行一下吧。当然这里编译的过程我们就不显示了,跟之前是一样的。并且为了方便,我们的模块名就不改了,还叫fubuki,但是编译之后的pyd文件内容已经变了,不过需要注意的是,编译之后会有一个build目录,然后会自动把里面的pyd文件拷贝到site-packages中,如果你修改了代码,但是模块名没有变的话,那么编译之后的文件名还和原来一样。如果一样的话,那么它发现已经存在相同文件了,就不会再拷贝了。因此两种做法:要么你把模块名给改了,这样编译会生成新的模块。要么编译之前记得把上一次编译生成的build目录先删掉,我们推荐第二种做法,不然site-packages目录下会出现一大堆我们自己定义的模块。

import fubuki

# 传参不符合,自动给你报错

try:

print(fubuki.f())

except TypeError as e:

print(e) # function takes exactly 4 arguments (0 given)

try:

print(fubuki.f(123))

except TypeError as e:

print(e) # function takes exactly 4 arguments (1 given)

try:

print(fubuki.f(123, "xxx", 123, 123))

except TypeError as e:

print(e) # an integer is required (got type str)

fubuki.f(123, 123, 123, 123) # int: 123, long: 123, float: 123.000000, double: 123.000000

怎么样,是不是很简单呢?当然PyArg_ParseTuple解析失败,Python底层自动帮你报错了,告诉你缺了几个参数,或者类型是哪里错了。

设置异常

下面我们介绍一下如何设置一个异常,我们还是定义一个函数,接收3个object对象。第一个参数类型是int、第二个参数类型是float、第三个参数类型是str。

#include "Python.h"

static PyObject *

f(PyObject *self, PyObject *args)

{

PyObject PyObject *obj1, *obj2, *obj3;

/*

Py_REFCNT: 获取引用计数

Py_TYPE: 获取类型

Py_SIZE: 获取ob_size

*/

int ob_size = Py_SIZE(args);

if (ob_size != 3){

//设置完异常之后直接return NULL即可,PyErr_Format用来设置异常,接收异常类型(PyExc_异常)、异常信息

PyErr_Format(PyExc_TypeError, "function f need takes 3 argument, but got %d", ob_size);

return NULL;

}

//得到三个PyObject *

if (!PyArg_ParseTuple(args, "OOO", &obj1, &obj2, &obj3)){

return NULL;

}

int arg1 = strcmp(Py_TYPE(obj1) -> tp_name, "int") == 0;

if (!arg1){

PyErr_Format(PyExc_TypeError, "arg1 require an integer, got %s", Py_TYPE(obj1) -> tp_name);

return NULL;

}

int arg2 = strcmp(Py_TYPE(obj2) -> tp_name, "float") == 0;

if (!arg2){

PyErr_Format(PyExc_TypeError, "arg2 require a float, got %s", Py_TYPE(obj2) -> tp_name);

return NULL;

}

int arg3 = strcmp(Py_TYPE(obj3) -> tp_name, "str") == 0;

if (!arg3){

PyErr_Format(PyExc_TypeError, "arg3 require an string, got %s", Py_TYPE(obj3) -> tp_name);

return NULL;

}

return PyUnicode_FromString("congratulations~~~");

}

static PyMethodDef fubuki_function[] = {

{

"f",

(PyCFunction)f,

METH_VARARGS, //这里记得一定要改成METH_VARARGS

"this is a function named f"

},

{NULL, NULL, 0, NULL}

};

static PyModuleDef fubuki_module = {

PyModuleDef_HEAD_INIT,

"fubuki",

"this a module named fubuki",

-1,

fubuki_function,

NULL, NULL, NULL, NULL

};

PyMODINIT_FUNC

PyInit_fubuki(void)

{

return PyModule_Create(&fubuki_module);

}

编译成扩展模块,调用一下。

import fubuki

# 传参不符合,自动给你报错

try:

print(fubuki.f())

except TypeError as e:

print(e) # function f need takes 3 argument, but got 0

try:

print(fubuki.f(123))

except TypeError as e:

print(e) # function f need takes 3 argument, but got 1

try:

print(fubuki.f(123, "xxx", 123, 123))

except TypeError as e:

print(e) # function f need takes 3 argument, but got 4

try:

print(fubuki.f(123, 123, "xxx"))

except TypeError as e:

print(e) # arg2 require a float, got int

print(fubuki.f(123, 123., "xxx")) # congratulations~~~

传递关键字参数

传递关键字参数的话,那么我们可以通过key=value的方式来实现,那么在C中我们如何解析呢?既然支持关键字的方式,那么是不是也可以实现默认的参数,就是我们不传会使用默认值呢?答案是支持的,我们知道解析位置参数是通过PyArg_ParseTuple,那么解析关键字参数是通过PyArg_ParseTupleAndKeywords

//函数原型

int PyArg_ParseTupleAndKeywords(PyObject *args, PyObject *kw, const char *format, char *keywords[], ...)

我们看到相比原来的PyArg_ParseTuple,多了一个kw和一个char *类型的数组,具体怎么用我们在编写代码的时候说。

#include "Python.h"

static PyObject *

f(PyObject *self, PyObject *args, PyObject *kw)

{

//我们说函数既可以通过位置参数、还可以通过关键字参数传递,那么函数的参数类型就要变成METH_VARARGS | METH_KEYWORDS

//假设我们定义了三个参数,name、age、place,这三个参数可以通过位置参数传递、也可以通过关键字参数传递

//这里我们先只使用纯英文,等到介绍到字符串的Python/C API的时候再说

char *name;

int age = -1;

char *where = "japan";

//告诉python解释器,参数的名字,注意:这里面字符串的顺序就是函数定义的参数顺序

//也是keys后面的变量顺序,其实变量名字叫什么无所谓,但是类型要和format中对应的占位符匹配,只是为了一致我们会起相同的名字

//注意结尾要有一个NULL,否则会报出段错误。

char *keys[] = {"name", "age", "place", NULL}; //这个NULL很重要

//解析参数,我们看到format中本来应该是sis的,但是中间出现了一个|

//这就表示|后面的参数是可以不填的,如果不填会使用我们上面给出的默认值

//因此这里name就是必填的,因为它在|的前面,而age和where可以不填,如果不填就用我们上面给出的默认值

//keys就是定义的参数的名字,后面把参数的指针传进去

if (!PyArg_ParseTupleAndKeywords(args, kw, "s|is", keys, &name, &age, &where)){

return NULL;

}

printf("name: %s, age: %d, place: %s\n", name, age, where);

return Py_None;

}

static PyMethodDef fubuki_function[] = {

{

"f",

(PyCFunction)f,

METH_VARARGS | METH_KEYWORDS,

"this is a function named f"

},

{NULL, NULL, 0, NULL}

};

static PyModuleDef fubuki_module = {

PyModuleDef_HEAD_INIT,

"fubuki",

"this a module named fubuki",

-1,

fubuki_function,

NULL, NULL, NULL, NULL

};

PyMODINIT_FUNC

PyInit_fubuki(void)

{

return PyModule_Create(&fubuki_module);

}

老规矩,还是编译成扩展模块。

import fubuki

try:

fubuki.f()

except TypeError as e:

print(e) # function missing required argument 'name' (pos 1)

fubuki.f("mea") # name: mea, age: -1, place: japan

fubuki.f("mea", 38) # name: mea, age: 38, place: japan

fubuki.f("mea", 38, "JAPAN") # name: mea, age: 38, place: JAPAN

以上我们便实现了支持关键字参数的函数,我们说使用PyArg_ParseTupleAndKeywords的时候,里面写上了args和kw,这表示既支持位置参数、也支持关键字参数。至于顺序就是代码中的顺序,我们看到如果参数传递的个数不正确,Python会自动提示你。

返回布尔类型和None

我们说函数都必须返回一个PyObject *,如果这个函数没有返回值,那么在Python中实际上返回的是一个None,但是我们不能返回NULL,None和NULL是两码事。在扩展函数中,如果返回NULL就表示这个函数执行的时候,不符合某个逻辑,我们需要终止掉,不能再执行下去了。这是在底层,但是在Python的层面,你需要告诉使用者为什么不能执行了,或者说底层的哪一行代码不满足条件,因此这个时候我们会在return NULL之前需要手动设置一个异常,这样在Python代码中就会报错,才知道为什么底层函数退出了。当然有时候会自动帮我们设置,比如上面的参数解析错误。

但是返回None的话,Python也提供了一个宏,我们直接写Py_RETURN_NONE;即可,表示返回一个None,当然也可以使用之前的return Py_None。如果是bool类型的话,那么就是return Py_True;和return Py_False;,比较简单,就不代码演示了。

引用计数和内存管理

下面来说一个非常关键的地方,就是引用计数和内存管理。我们目前都没有涉及到内存管理的操作,但是我们说Python中的对象都是申请在堆区的,这个是不会自动释放的。

static PyObject *

my_func1(PyObject *self, PyObject *args, PyObject *kw)

{

PyObject *s = PyUnicode_FromString("你好呀~~~");

return Py_None;

}

这个函数不需要参数,如果我们写一个死循环不停的调用这个函数,你会发现内存的占用蹭蹭的往上涨。就是因为这个PyUnicodeObject是申请在堆区的,此时内部的引用计数为1。函数执行完毕变量s被销毁了,但是s是一个指针,这个指针被销毁了是不假,但是它指向的内存并没有被销毁。

static PyObject *

my_func1(PyObject *self, PyObject *args, PyObject *kw)

{

PyObject *s = PyUnicode_FromString("你好呀~~~");

Py_DECREF(s);

return Py_None;

}

因此我们需要手动调用Py_DECREF这个宏,来将s指向的PyUnicodeObject的引用计数减1,这样引用计数就为0了。不过有一个特例,那就是当这个指针作为返回值的时候,我们不需要手动减去引用计数,因为会自动减。

static PyObject *

my_func1(PyObject *self, PyObject *args, PyObject *kw)

{

PyObject *s = PyUnicode_FromString("你好呀~~~");

//如果我们把s给返回了,那么我们就不需要调用Py_DECREF了

//因为一旦作为返回值,那么会自动减去1

//所以C中的对象是由python来管理的,准确的说应该是作为返回值的指针指向的对象是由python来管理的

return s;

}

不过这里还存在一个问题,那就是我们在C中返回的是Python传过来的

static PyObject *

my_func1(PyObject *self, PyObject *args, PyObject *kw)

{

PyObject *s = NULL;

char *keys[] = {"s", NULL};

PyArg_ParseTupleAndKeywords(args, kw, "O", keys, &s);

//传递过来一个PyObject *,然后原封不动的返回

return s;

}

显然上面s指向的内存不是在C中调用api创建的,而是Python创建然后传递过来、解析出来的,也就是说这个s在解析之后已经指向了一块合法的内存。但是内存中的对象的引用计数是没有变化的,虽说有新的变量(这里的s)指向它了,但是这个s是C中的变量不是Python中的变量,因此你可以认为它的引用计数是没有变化的。然后作为返回值返回之后,指向对象的引用计数减一。所以你会发现在Python中,创建一个变量,然后传递到my_func1中,执行完之后再进行打印就会发生段错误,因为对应的内存已经被回收了。如果能正常打印,说明在Python中这个变量的引用计数不为1,可能是小整数对象池、或者有多个变量引用,那么就创建一个大整数或者其他的变量多调用几次,因为作为返回值,每次调用引用计数都会减1。

static PyObject *

my_func1(PyObject *self, PyObject *args, PyObject *kw)

{

//假设创建一个PyListObject

PyObject *l1 = PyList_New(2);

//将l1赋值给l2,但是不好意思,这两位老铁指向的PyListObject的引用计数还是1

PyObject *l2 = l1;

return s;

}

因此我们说,如果在C中创建一个PyObject的话,那么它的引用计数只会是1,因为对象被初始化了,引用计数默认是1。至于传递,无论你在C中将创建PyObject返回的指针赋值给了多少个变量,它们指向的PyObject的引用计数都会是1。因为这些变量是C中的变量,不是Python中的。

因此我们的问题就很好解释了,我们说当一个PyObject *作为返回值的时候,它指向的对象的引用计数会减去1,那么当Python传递过来一个PyObject *指针的时候,由于它作为了返回值,因此引用计数会减1。因此当你在Python中调用扩展函数结束之后,这个变量指向的内存可能就被销毁了。如果你在Python传递过来的指针没有作为返回值,那么怎么引用计数是不会发生变化的,但是一旦作为了返回值,引用计数会自动减1,因此我们需要手动的加1。

static PyObject *

my_func1(PyObject *self, PyObject *args, PyObject *kw)

{

PyObject *s = NULL;

char *keys[] = {"s", NULL};

PyArg_ParseTupleAndKeywords(args, kw, "O", keys, &s);

//这样就没有问题了。

Py_INCREF(s);

return s;

}

因此我们可以得出如下结论:

如果在C中,创建一个PyObject *var,并且var已经指向了合法的内存,比如调用PyList_New、PyDict_New等等api返回的PyObject *,总之就是已经存在了PyObject,那么如果var没有作为返回值,我们必须手动地将var指向的对象的引用计数减1,否则这个对象就会在堆区一直待着不会被回收。可能有人问,如果PyObject *var2 = var,我将var再赋值给一个变量呢?那么只需要对一个变量进行Py_DECREF即可,当然对哪个变量都是一样的,因为在C中变量的传递不会导致引用计数的增加。

如果C中创建的PyObject *作为返回值而存在了,那么会自动将指向的对象的引用计数减1,因此此时该指针指向的内存就由Python来管理了,就相当于在Python中创建了一个对象,我们不需要关心。

最后关键的一点,如果C中返回的指针指向的内存是Python中创建好的,假设我们在python中创建了一个对象,然后把指针传递过来了,但是我们说这不会导致引用计数的增加,因为赋值的变量是C中的变量。如果C中用来接收参数的指针没有作为返回值,那么引用计数在扩展函数调用之前是多少、调用之后还是多少。一旦作为了返回值,我们说引用计数会自动减1,因此假设你在调用扩展函数之前引用计数是3,那么调用之后你会发现引用计数变成了2。为了防止段错误,一旦作为返回值,我们需要在返回之前手动地将引用计数加1。

C中创建的:不作为返回值,引用计数手动减1、作为返回值,不处理;Python中创建传递过来的,不作为返回值,不处理、作为返回值,引用计数手动加1。

另外关于Py_INCREF和Py_DECREF,它们要求PyObject *不可以为NULL,如果可能为NULL的话,那么建议使用Py_XINCREF和Py_XDECREF。当然虽然我们演示使用的是Py_INCREF和Py_DECREF,但是我们建议实际项目中都使用Py_XINCREF和Py_XDECREF。

小结

这次我们介绍了Python编写扩展模块的基本骨架,至于一些类型对象的API,前面已经剖析过了,所以一些API的调用可以自己去源码中查找,或者查阅官网。只不过最重要的是,我们需要先了解编写扩展所对应的流程是什么,以及函数的位置参数、关键字参数、设置异常、引用计数等等,这些都是贯穿始终的,只有先掌握这些,才能继续往下学习。

后续我们将介绍Python的内存管理。

编写python扩展模块_《深度剖析CPython解释器》27. 使用Python/C API编写扩展模块:编写扩展模块的整体流程...相关推荐

  1. python深度讲解_《深度剖析CPython解释器》21. Python类机制的深度解析(第五部分): 全方位介绍Python中的魔法方法,一网打尽...

    楔子 下面我们来看一下Python中的魔法方法,我们知道Python将操作符都抽象成了一个魔法方法(magic method),实例对象进行操作时,实际上会调用魔法方法.也正因为如此,numpy才得以 ...

  2. go test 如何输出到控制台_深度剖析 Go 中的 Go 协程 (goroutines) -- Go 的并发

    Go 协程 (goroutine) 是指在后台中运行的轻量级执行线程,go 协程是 Go 中实现并发的关键组成部分. 在上次的课程中,我们学习了 Go 的并发模型.由于 Go 协程相对于传统操作系统中 ...

  3. 如何将c语言程序封装供python调用_转:用C语言扩展Python的功能

    一.简介 Python是一门功能强大的高级脚本语言,它的强大不仅表现在其自身的功能上,而且还表现在其良好的可扩展性上,正因如此,Python已经开始受到越来越多人的青睐,并且被屡屡成功地应用于各类大型 ...

  4. 汽车行业会用到python吗_二手车行业的猫腻,用Python都扒出来了

    二手车行业由来已久,但是一直都没有被互联网化,随着人民生活水平日益提高,互联网技术的成熟,最近各大二手车平台纷纷崛起. 其中以瓜子和人人车为代表的二手车平台已经获得了数亿美元的融资,战争已经打响了,二 ...

  5. 微软 python教程_最强福利——来自微软的Python学习教程(开发指南)

    各位小伙伴们,大家有多久没有发现柳猫这么勤奋的更新啦~ 今天给小伙伴们带来微软的官方福利,你没看错,就是来自微软的官方Python学习教程(开发指南)~ 之前微软上线过一套 Python 教程< ...

  6. 初学者不建议月python吗_为什么我不建议你将python作为入门编程语言

    现在流行的编程语言里,python的热度可谓是热的通红,python以其短小精悍的语法.以其高效的开发,简单入门作为亮点,迅速的在各个领域占有一席之地. 然而,无论你说python有多好,我都不建议你 ...

  7. import python as mulan_深度学习(一)搭建 python 环境

    简介 python 用来编码的包(package)数不胜数,常用的包可能有几十个,自己配置的话估计会不胜其烦.当然,用什么就下什么的话也不是很麻烦,只是要费点时间. 但是,我推荐怎么简单怎么来,毕竟搭 ...

  8. python 快速排序_小白入门知识详解:Python实现快速排序的方法(含实例代码)...

    前言: 今天为大家带来的内容是:小白入门知识详解:Python实现快速排序的方法(含实例代码)希望通过本文的内容能够对各位有所帮助,喜欢的话记得点赞转发收藏不迷路哦!!! 提示: 这篇文章主要介绍了P ...

  9. 在线python视频教程_【好程序员】2019 Python全套视频教程2

    2019千锋好程序员全新Python教程,深入浅出的讲解Python语言的基础语法,注重基本编程能力训练,深入解析面向对象思想,数据类型和变量.运算符.流程控制.函数.面向对象.模块和包.生成器和迭代 ...

最新文章

  1. 招聘|华为诺亚方舟实验室AI算法实习生
  2. Bootsrap基本应用
  3. osgi java_使普通的旧Java OSGi兼容
  4. ls命令查看文件和目录列表
  5. Xcode 8 Swift 类似插件方法
  6. 【Java就业培训教程】——String类的学习
  7. “缺少winload.efi”的解决办法“:Windows Boot Manager更改读取启动信息路径
  8. 用c语言写双人贪吃蛇,试图写了一个双人贪吃蛇,结果蛇竖着跑正常,横着跑就只有头了,求解~...
  9. 基于unity+vuforia的VR二级齿轮减速器动画分解
  10. 基于统计语言模型的拼音输入法
  11. 如何用Python操作Excel自动化办公?一个案例教会你openpyxl——图表设计和透视表
  12. java获取网页源文件
  13. IE浏览器跳转无效的问题
  14. HTML5期末大作业:订餐系统网站设计——绿色的网上订餐系统(23个页面) 网页作品 订餐系统网页设计作业模板 学生网页制作源代码下载
  15. charts中各种图演示
  16. 【Web技术】前端水印实现方案
  17. Web在线聊天室(2) --- 技术实现原理
  18. egg-sequelize使用教程
  19. OpenLayers基础:多方底图
  20. [翻译]Learning Multiple Tasks with Deep Relationship Networks

热门文章

  1. mysql 左连接_MySQL左连接与右连接
  2. asp.net文件上传进度条控件(破解版~没时间限制)多项自定义 .
  3. ML《决策树(一)ID3》
  4. [机器学习-实践篇]学习之线性回归、岭回归、Lasso回归,tensorflow实现的线性回归
  5. 深度学习- 激活函数总结(Sigmoid, Tanh, Relu, leaky Relu, PReLU, Maxout, ELU, Softmax,交叉熵函数)
  6. java 注册成功跳转,写了个注册页面填了注册信息后点注册按钮居然不跳转,为什么?...
  7. postman 使用_postman如何使用集合断言?
  8. leetcode - 931. 下降路径最小和
  9. Minimum Mean Squared Error (MMSE)最小均方误差
  10. Matlab--二次多项式曲面拟合