以下为个人学习笔记整理

# 编写 Python 的 C 扩展

# 构建编译基础环境

  • 启动 visual studio installer
  • 安装相关插件和依赖库

image-20201009163830574

# 编写 C 扩展代码

简单的 C 扩展大致结构图:

image-20201009182911183

  • 定义函数: PyMethodDef
  • 定义模块: PyModuleDef
  • 定义初始化模块函数:PyInit_module

# 定义函数

  • 函数名: 模块名_函数名

  • 参数类型 ml_flags

    • METH_VARARGS :只包含元组参数 def func(self, *args)
    • METH_VARARGS | METH_KEYWORDS :包含元组及字典参数 def func(self, *args, **kwargs)
    • METH_FASTCALL :固定个数的元组参数。顾名思义,采用快速调用效率较高。 def func(self, arg1, arg2, argN)
    • METH_FASTCALL | METH_KEYWORDS :固定个数元组参数 + 字典参数。 def func(self, arg1, arg2, argN, **kwargs)
    • METH_NOARGS :无参函数。 def func(self)
    • METH_O :单个参数。 def func(self, arg1)
    • METH_CLASS :类函数。 def func(cls)
    • METH_STATIC :静态函数。 def func()
  • 逻辑代码

    • 参数解析(PyArg_Parse):

      • PyArg_ParseTuple :解析位置参数。

        • PyArg_ParseTuple(PyObject* args, const char* format, ...)
        int i,j;
        char *s;
        PyArg_ParseTuple(args,"iis", &i, &j, &s)
            
        // Python call:func(i:int, j:int, s:str)
      • PyArg_VaParse:用于解析可变数量参数,通过 var_list 承载。

        • PyArg_VaParse(PyObject* args, const char* format, va_list vargs)
        • 相关使用案例
      • PyArg_ParseTupleAndKeywords:解析位置和关键字参数。

        • PyArg_ParseTupleAndKeywords(PyObject* args, PyObject* kw, const char* format, char* keywords[], ...)
        char *a;
        char *b;
        char *foo = NULL;
        char *bar = NULL;
        char *baz = NULL;
        static char *keywords[] = {"a","b","foo","bar","baz", NULL};
        PyArg_ParseTupleAndKeywords(args, kwargs,"ss|sss", keywords, &a, &b, &foo, &bar, &baz)
            
        // Python call:
        // func() // Fails, require a and b
        // func('a') // fails, requires b
        // func('a', 'b')
        // func('a', 'b', foo='foo', bar='bar', baz='baz) 
        // func('a', 'b','foo', 'bar', 'baz')
        // func(a='a', b='b', foo='foo', bar='bar', baz='baz')
      • PyArg_VaParseTupleAndKeywords :解析可变数量参数和关键字参数。

        • PyArg_VaParseTupleAndKeywords(PyObject* args, PyObject* kw, const char* format, char* keywords[], va_list vargs)
      • PyArg_ValidateKeywordArguments :解析关键字参数,关键字必须为字符串

        • PyArg_ValidateKeywordArguments(PyObject* kw)
      • PyArg_UnpackTuple :解析特定长度范围的元组。

        • PyArg_UnpackTuple(PyObject* args, const char* name, Py_ssize_t* min, Py_ssize_t* max, ...)
  • 返回值 —— Py_BuildValue

    • PyObject *Py_BuildValue(const char *format, ...)
    Py_BuildValue("")                        None
    Py_BuildValue("i", 123)                  123
    Py_BuildValue("iii", 123, 456, 789)      (123, 456, 789)
    Py_BuildValue("s", "hello")              'hello'
    Py_BuildValue("y", "hello")              b'hello'
    Py_BuildValue("ss", "hello", "world")    ('hello', 'world')
    Py_BuildValue("s#", "hello", 4)          'hell'
    Py_BuildValue("y#", "hello", 4)          b'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}", "abc", 123, "def", 456)    {'abc': 123, 'def': 456}
    Py_BuildValue("((ii)(ii)) (ii)", 1, 2, 3, 4, 5, 6)          (((1, 2), (3, 4)), (5, 6))
  • format 格式:

字符类型含义
sconst char *字符串
s*Py_buffer生成的 C 字符串可能包含嵌入的 NULL 字节
s#const char *,int拆解出两个对象,一个 str,一个 len
zconst char *字符串或者 None
z*Py_buffer生成的 C 字符串可能包含嵌入的 NULL 字节或者 None
z#const char *,int拆解出两个对象,一个 str 或者 None,一个 len
yconst char *只读字节对象
SPyBytesObject *python 的 bytes 对象
YPyByteArrayObject *Python 的 bytearray 对象
bint无符号整数
Bint无符号整数且不进行溢出检查
hint短整型
Hint不进行溢出检查的短整型
iint正常整数
llong intC 的长整形
cchar字符
ddouble双精度浮点
ffloat单精度浮点
pboolbool 类型本质上还是 int
DPy_complex *复数
(...)tuple序列
[...]list列表
{...}dict字典
OPyObject*Python 对象,不修改其引用计数
O&convert+void*将 Python 对象转为 C。 C-obj = converter(py-obj, O&);
\|表示后续参数是可选的 i|s : 一个整数,一个字符串。字符串可选
$表示后续参数仅是关键字
:格式单位列表到此结束;在错误消息中使用冒号后的字符串作为函数名
;格式单位列表到此结束;分号后的字符串用作错误消息。 :; 相互排斥。

# exmaple:

# 定义 Python 的 C 扩展
[ccore.c]
# include <Python.h>
static PyObject* ccore_calc(PyObject* self){
    return Py_BuildValue("i",100);
}
struct PyMethodDef module_methods[] = {
    {"calc",(PyCFunction)ccore_calc, METH_NOARGS, "这是一个简单测测试"},
    {NULL, NULL, 0, NULL}
};
static struct PyModuleDef modulemethod = {
  PyModuleDef_HEAD_INIT,
  "ccore",           /* name of module */
  "A sample module",  /* Doc string (may be NULL) */
  -1,                 /* Size of per-interpreter state or -1 */
  module_methods       /* Method table */
};
PyMODINIT_FUNC PyInit_ccore(void)
{
    return PyModule_Create(&modulemethod);
}
[setup.py]
from distutils.core import setup
from distutils.extension import Extension
setup(name='Py_ccore',
      version='1.0',
      ext_modules=[Extension('ccore', ['ccore.c'])],
      )
# 构建指令
python setup.py install
# 调用 Python 的 C 扩展
import ccore
print(ccore.calc())
Out:100

# 定义 PyMethodDef

  • C 层的定义
struct PyMethodDef {
   char *ml_name; 
   PyCFunction ml_meth; 
   int ml_flags; 
   char *ml_doc; 
};
字段类型含义
ml_nameconst char *函数名
ml_methPyCFunction函数指针
ml_flagsint参数类型
ml_docconst char *函数说明

# C 层的使用:

struct PyMethodDef module_methods[] = {
    {"calc",(PyCFunction)ccore_calc, METH_NOARGS, "func.__doc__"},
    # ....
    {NULL, NULL, 0, NULL}
};

等价于定义了一个如下结构的函数,不包含实现和返回值

def calc():
    """func.__doc__""""
    ...

# 定义 PyModuleDef

  • C 层的定义
struct PyModuleDef {
    PyModuleDef_HEAD_INIT,
   	char *m_name; 
    char *m_doc;
   	Py_ssize_t m_size; //。
   	PyMethodDef * m_methods; 
}
字段类型含义
m_basePyModuleDef_Base始终将此成员初始化为 PyModuleDef_HEAD_INIT
m_nameconst char *模块名
m_docconst char *模块说明
m_sizePy_ssize_t设置存储模块, -1 表示全局状态,不支持子解释器。
更多详细信息,请参见 PEP 3121
m_methodsPyMethodDef *函数列表指针

# C 层的使用:

static struct PyModuleDef modulemethod = {
  PyModuleDef_HEAD_INIT,
  "ccore", 
  "A sample module",  
  -1,     
  module_methods       
};

等价于定义了一个如下结构的模块

[ccore.py]
"""A sample module"""
def calc():
    """func.__doc__""""
    ...
    
...

# 定义初始化模块函数

如下代码定义模块的创建

PyMODINIT_FUNC PyInit_ccore(void) // PyInit_模块名
{
    return PyModule_Create(&modulemethod); // modulemethod is PyModuleDef obj
}

参考资料:

Python 官方手册