lovingxiaobing 发表于 2018-12-21 16:44

用C/C++写Python的扩展模块

许多小伙伴都在用Python,你们可能知道的是,写Python代码,然后用可以运行python的工具运行就可以了,python的便利性毋庸置疑!
但是大家可能有的知道或者不知道,我们平时用的那个Python叫做CPython,它的解释器是用C实现的,Python只是一种语言规范,只要能按照他的规范实现的解释器都可以是Python,比如现在大家普遍所用的CPython;还有其他的可以实现Python语言规范解释器,比如Java写的Jython、C#写的IronPython(基于mono框架,可以跨平台)、CLPython 是用 Common Lisp实现的一个解释器、还有更狠的就是用Python代码来写Python解释器(PyPy)!!!


既然现在普遍使用的是CPython那么,学会如何写Python的C/C++扩展模块也是个提高Python代码运行速度的一项不错的技能!


先来了解一下Python的一些文件的后缀名:
*.py    : 这个相信在做的各位都知道,Python源代码文件嘛(普通的文本文件)
*.pyw: 这个可能不常见,因为这个一般只有在有窗口桌面环境才用的比较多,*.pyw与*.py不同的是:前者在启动时不会显示控制台窗口,后者会显示控制台窗口。在windows下,*.py的启动器是python.exe;*.pyw的启动器是pythonw.exe,更多的细节官网都有说明
*.pyc: 是经过编译的字节码脚本文件,编译过后的文件比还是文本文件的*.py运行速度更快,因为*.py是边解释,边运行
*.pyd: 这个是Python的C/C++扩展模块文件,其实就是个动态链接库,只不过改名成*.pyd


*.pyd是这篇文章要讲的。


这篇文章其实东西不多,更多的是教大家如何去看文档,因为在CSDN文章满天飞的今天,官方文档更具有参考价值!
我这里用的是Python3.7.1,文档自然也是python3.7的文档,大家看文档可以去官方文档库,可以以在本地看,因为安装python时就已经带有一个*.chm类型的文档:
就在开始菜单找到python的快捷目录,也可以到Python的安装目录中找到一个名为Doc的目录中找,就一个文件。帮助文件的文件名为python371.chm。
下面是开始菜单中那个帮助文件的名字:



打开帮助文件会看到一大堆鸟文,emmmmm
不慌,有翻译工具!
打开帮助文档,画框框的步骤主要是讲Python的C/C++扩展以及将Python嵌入到自己的引用程序中,大家应该都用过Sublime Text,这个编辑器就是Python嵌入到应用程序中的例子。
下面是文档:


首先,大家都知道有一句话Python中都是对象,大到复杂的对象,小到一个数值,None等都是对象,在写python的C/C++扩展模块时,这句话就体现出来了!在C/C++ 中是一个结构体:PyObject
不过我们都是使用PyObject结构体指针PyObject*,这点需要注意!。

CPython的对外接口名字命名的很人性化,比如万恶的对象的API命名为PyObject_*、内存管理函数PyMen_*、数值(包括整数和浮点数的运算等)PyNumber_*、浮点数PyFloat_*、整数PyLong_*、序列PySequence_*、列表PyList_*、元组PyTuple_*、字典PyDict_*、集合PySet_*、可迭代对象PyIter_*、字符串PyUnicode_*、函数参数PyArg_*、函数PyFunction_*、甚至是文件对象PyFile_*等等名字一看就知道是关于什么类型的对象的操作的API。





大家也应该发现了,几乎每个API,只要返回值类型是PyObject*的都会有“Return value: New reference.”和“Return value: Borrowed reference.”这两句话,啥意思呢。
首先得说一下的就是python中的垃圾回收机制是靠引用计数来维持,当你对一个对象进行使用时就代表正在使用,不能销毁,写Python的C/C++扩展模块时,对一个对象进行引用计数+1用函数Py_INCREF对对象引用计数-1用函数Py_DECREF,将对象的引用计数置0用Py_Clear,当一个对象的引用计数为0,那么这个对象也就寿终正寝了!回过头来所一下“Return value: New reference.”和“Return value: Borrowed reference.”,如果文档中的API中说明了“Return value: New reference.” 那么就代表返回的是一个新的对象,此时对象的引用计数为1,如果你不适用这个对象了就得必须将其引用计数值0或者-1;如果文档中的API说明了“Return value: Borrowed reference.” 说明返回的对象不是一个新的对象,是对某一个对象的引用,此时你就要当心引用计数了,这个得看你Python基础啦,某个对象什么时候不需要了,什么时候还需要几乎掌控在咱们手中!还有一个就是,当你使用Py_INCREF或者Py_DECREF时还发现了类似的函数,多了个X比如Py_XINCREF或者Py_XDECREF,带不带X的区别就是带X的可以处理当传入值为NULL的PyObject指针,我也推荐使用这两个带X的。


现在先不讨论如果将C函数导入到Python中,先会写,如果有小伙伴有整过Lua的C扩展模块,那么Python的也很好理解。



写之前先来了解一下导出函数的格式:

PyObject* C函数名(PyObject* self, PyObject* args) {
    //TODO:实现代码
    return (PyObject*类型的返回值)
}


从上面的函数格式的参数中看到两个参数self和args,当这个C函数时一个普通Python函数时,self为Python的内部对象;当这个C函数为一个类成员方法时,self大家懂的,就是对象本身。args一般是一个元组类型的对象,Python通过这个args给C函数传参数。

先来看一小段的代码:

mylist =

for index in range(len(mylist)):
    v = mylist
    print(v)


这大家都熟悉,就是遍历打印出列表中的内容嘛。
接下来我们在C中实现这个功能:




上面的代码中,我们看到函数的定义前有一个static,在c中,函数在动态链接库中默认导出,如果不想导出就添加static修饰;但在C++中就不会导出,在C++中我们就得通过__decspec(dllexport)修饰这个函数才能导出,这个先了解就行。
接下来看到PyArg_ParseTuple函数,第一个参数就是args指针,第二个参数是format字符串,第三个参数是一个可变长参数,是通过format字符串知道需要多少个参数,这个就类似于C中的fscanf函数,python有自己的一套格式化标记,其中O代表Python中的所有对象,Python一切都是对象,list也是对象!
PyArg_ParseTuple用来解析args元组,将参数转换成PyObject指针。与PyArg_ParseTuple对应的就是Py_BuildValue,这个函数是用来创建Python对象的,i代表整数的最后一个参数也是可可变长参数。具体的format字符串的格式请查询文档!当出错时候可以直接返回NULL指针~~
代码中的第二个函数PyList_Check是用来检查对象是否是列表。
第三个函数PyList_Size 是用来获取列表的长度的
第四个函数PyList_GetItem 是用来获取列表中某个索引的值,是“Return value: Borrowed reference.”, 不要随便Py_XDECREF哟~~~
第四个函数PyObject_Str 是用来调用对象的魔法方法__str__的,相信大家学习魔法方法时都会一脸懵逼,等你学会一点后会发现这玩意真的强大,__str__是每个对象都有的,大家平时自己写__str__进去时,是对系统的默认的进行重写!函数返回的对象是“Return value: New reference.”, 使用后不用了记得将其引用计数-1或者置0。
第五个函数PyUnicode_AsWideCharString 是将Py字符串对象转换成C宽字符,Py字符串并不只能转换成C的char类型字符串,大家可以自行去查文档。PyUnicode_*一类的函数专门对字符串进行处理的!
第五个函数PyMem_Free 是用来释放不用的C字符串的,当你调用PyUnicode_AsWideCharString获取到C字符串后可以随意使用,是独立与python的,不受Python控制,在不用后记得调用PyMem_Free及时将其释放
更多的细节得多看看文章怎么说。


下面的遍历元组也是同样道理:




然后是遍历字典:



/*
*遍历字典
*/
static PyObject* WalkingSequence_WalkingDownDict(
    PyObject* self,
    PyObject* args
) {

    //声明一个Python对象(字典)
    PyObject* mydict;

    //dict是一个Python对象
    if (!PyArg_ParseTuple(args, "O", &mydict))
      return NULL;

    //检查是否是一个字典
    if (!PyDict_Check(mydict))
      return NULL;

    /* 字典是有限集,可以获取长度 */
    Py_ssize_t dict_len = PyDict_Size(mydict);
    cout << "字典内元素个数: " << endl;

    PyObject* key;
    PyObject* value;
    /* 开始前pos必须初始化为0 */
    Py_ssize_t pos = 0;

    //遍历完返回0
    while (PyDict_Next(mydict, &pos, &key, &value)) {
      //管他啥类型,直接调用其__str__魔法方法,返回它本来该输出的样子
      //这里得注意的是,字典的键不一定是字符串
      PyObject* py_str_key = PyObject_Str(key);
      if (py_str_key) {
            wchar_t* wc_str_key = PyUnicode_AsWideCharString(py_str_key, NULL);
            if (wc_str_key) {
                //输出键
                wcout << wc_str_key;
                PyMem_Free(wc_str_key);
            } else {
                cerr << "#dict:" << endl;
                Py_XDECREF(py_str_key);
                continue;
            }
            Py_XDECREF(py_str_key);
      } else {
            cerr << "#dict:" << endl;
            continue;
      }

      //输出冒号
      cout.put(':');

      /* 下面的步骤和上面的一样 */
      PyObject* py_str_value = PyObject_Str(value);
      if (py_str_value) {
            wchar_t* wc_str_value = PyUnicode_AsWideCharString(py_str_value, NULL);
            if (wc_str_value) {
                //输出值
                wcout << wc_str_value << endl;;
                PyMem_Free(wc_str_value);
            } else {
                cerr << "#dict:" << endl;
            }
            Py_XDECREF(py_str_value);
      } else {
            cerr << "#dict:" << endl;
            continue;
      }
    }


    //Long类型对应的是Python中的整数
    return PyLong_FromSsize_t(dict_len);
}


在上面的代码中我们看到了一个陌生的函数:
函数PyDict_Next 是用来遍历字典的,传进两个PyObject*的地址,用来接收key和value,在文档中也说明了返回的对象是新对象还是引用对象:

在文档中给出的例子也简单明了!特别注意的是pos的起始值必须为0!
其余的自行查文档!



后面这里简单说一下遍历集合,大家高中都学过集合三要素:无序性、确定性、有限性。
既然是无序性的那么肯定是不能靠索引去获取元素了,咋办呢?
在Python中我们可以用for element in myset:...的方法去遍历集合,或者myiterator=iter(myset);for element in myiterator:...的方式,我们现在将的都是一些经常用的可迭代对象,python中的可迭代对象非常多,包括字符串等都是可迭代对象,那么就意味着我们有一个通用的方法去表里这个对象!
我们可以使用PyIter_*一类的函数去遍历这些可迭代对象!


看代码:




在文档中已经有非常详详细的注释了,这里就不再赘述~~~


这里简单说一下函数PyIter_Next,当可迭代对象遍历完后会抛出StopIteration异常,相应的PyIter_Next会返回NULL,那么就结束循环
函数PyLong_FromSsize_t用来将一个无符号数转换成Python的整数对象!


最后就是将我们写的这些个C函数导入到Python中了,我们的Python是通过一张函数表找到相应的C函数,并且调用!


这里定义一张表:



这张表的作用就是将C函数与Python函数建立映射关系
这张表的元素是一个PyMethodDef结构体类型的表,文档也有对这个结构的每个字段的详细注释,以及如果赋值,值的类型:





PyMethodDef结构的字段中:
第一个字段(ml_name): 是python中这个函数的名字
第二个字段(ml_meth): 是这个函数的C函数指针
第三个字段(ml_flags): 这个是如何构造函数的参数
第四个字段(ml_doc): 这个就是大家熟悉的文档了,就是通过help()函数获取的文档,大家学习python的过程中也使用过这个函数,这个ml_doc就是这个作用。


再填完所有的元素后我们得用一个空值的结构体作为结尾,就像刚才的代码中的那样,也可以直接{NULL},这样子效果也一样!


定义完映射表还没能导入到Python中,等会要将的也是Python3.x与Python2.x的区别。


在写Python3.x的的C/C++扩展模块时,我们得再写一张类似与模块描述表的一张表:




这样表一般就这样声明啦,这是一般的普遍的用法,特殊用法自行查文档。


最最后就是最后一步啦,我们我们得有一个接口暴露给Python,让她知道我们的那玩意在这(别想歪~~~)。Python会从接口函数的返回值获取到函数方法表。


这个接口函数也是有固定的格式的:





大家可以去看看宏PyMODINIT_FUNC中已经有PyObject*啦,真的是Python中到处是对象,模块都不放过!!!


最后是所讲的全部代码:



/*
*author: loxingxiaobing|小冰哟
*/

#include <iostream>
#include <Python.h>

/* 我这里使用的是python3.7.1版本的库 */
#pragma comment(lib, "Python37.lib")

using namespace std;

/*
*遍历列表
*/
static PyObject* WalkingSequence_WalkingDownList(
    PyObject* self,
    PyObject* args
) {

    //声明一个Python对象(列表)
    PyObject* mylist;

    //list是一个Python对象
    if (!PyArg_ParseTuple(args, "O", &mylist))
      return NULL;

    //检查是否是一个列表
    if (!PyList_Check(mylist))
      return NULL;

    //获取长度
    Py_ssize_t list_len = PyList_Size(mylist);
    cout << "列表长度: " << list_len << endl;

    for (Py_ssize_t index=0; index < list_len; index++) {

      //在Python中是mylist中的形式获取元素,这里的做法一模一样
      PyObject* item = PyList_GetItem(mylist, index);

      //元素获取失败
      if (!item) {
            cerr << "#list:" << endl;
            continue;
      }

      //这里直接调用Python对象的魔法方法__str__获取这个类型本该输出的样子
      PyObject* py_str = PyObject_Str(item);

      //函数调用成功返回非NULL的地址
      if (py_str) {
            //将字符串对象转换为C宽字符串
            wchar_t* wc_str = PyUnicode_AsWideCharString(py_str, NULL);
            if (wc_str) {
                //输出宽字符串
                wcout << L"list~[" << index << "]: " << wc_str << endl;
                //记得释放
                PyMem_Free(wc_str);
            } else {
                // 获取字符串出错!
                cerr << "#List:" << endl;
            }
            //引用计数-1,表示本次获取到的字符串对象已经不用了
            Py_XDECREF(py_str);
      } else {
            // 执行对象__str__方法失败
            cerr << "#List:" << endl;
      }
    }

    //返回列表长度
    return Py_BuildValue("i", list_len);
}


/*
*遍历元组
*/
//这里的做法跟元组的几乎无差别(直接是拷贝过来稍稍修改)
static PyObject* WalkingSequence_WalkingDownTuple(
    PyObject* self,
    PyObject* args
) {

    //声明一个Python对象(元组)
    PyObject* mytuple;

    //tuple是一个Python对象
    if (!PyArg_ParseTuple(args, "O", &mytuple))
      return NULL;

    //检查是否是一个元组
    if (!PyTuple_Check(mytuple))
      return NULL;

    //获取长度
    Py_ssize_t tuple_len = PyTuple_Size(mytuple);
    cout << "元表长度: " << tuple_len << endl;

    for (Py_ssize_t index=0; index < tuple_len; index++) {

      //在Python中是mytuple中的形式获取元素,这里的做法一模一样
      PyObject* item = PyTuple_GetItem(mytuple, index);

      //元素获取失败
      if (!item) {
            cerr << "#tuple:" << endl;
            continue;
      }

      //这里直接调用Python对象的魔法方法__str__获取这个类型本该输出的样子
      PyObject* py_str = PyObject_Str(item);

      //函数调用成功返回非NULL的地址
      if (py_str) {
            //将字符串对象转换为C宽字符串
            wchar_t* wc_str = PyUnicode_AsWideCharString(py_str, NULL);
            if (wc_str) {
                //输出宽字符串
                wcout << L"tuple~[" << index << "]: " << wc_str << endl;
                //记得释放
                PyMem_Free(wc_str);
            } else {
                // 获取字符串出错!
                cerr << "#tuple:" << endl;
            }
            //引用计数-1,表示本次获取到的字符串对象已经不用了
            Py_XDECREF(py_str);
      } else {
            // 执行对象__str__方法失败
            cerr << "#tuple:" << endl;
      }
    }

    //返回元组长度
    return Py_BuildValue("i", tuple_len);
}


/*
*遍历字典
*/
static PyObject* WalkingSequence_WalkingDownDict(
    PyObject* self,
    PyObject* args
) {

    //声明一个Python对象(字典)
    PyObject* mydict;

    //dict是一个Python对象
    if (!PyArg_ParseTuple(args, "O", &mydict))
      return NULL;

    //检查是否是一个字典
    if (!PyDict_Check(mydict))
      return NULL;

    /* 字典是有限集,可以获取长度 */
    Py_ssize_t dict_len = PyDict_Size(mydict);
    cout << "字典内元素个数: " << dict_len << endl;

    PyObject* key;
    PyObject* value;
    /* 开始前pos必须初始化为0 */
    Py_ssize_t pos = 0;

    //遍历完返回0
    while (PyDict_Next(mydict, &pos, &key, &value)) {
      //管他啥类型,直接调用其__str__魔法方法,返回它本来该输出的样子
      //这里得注意的是,字典的键不一定是字符串
      PyObject* py_str_key = PyObject_Str(key);
      if (py_str_key) {
            wchar_t* wc_str_key = PyUnicode_AsWideCharString(py_str_key, NULL);
            if (wc_str_key) {
                //输出键
                wcout << wc_str_key;
                PyMem_Free(wc_str_key);
            } else {
                cerr << "#dict:" << endl;
                Py_XDECREF(py_str_key);
                continue;
            }
            Py_XDECREF(py_str_key);
      } else {
            cerr << "#dict:" << endl;
            continue;
      }

      //输出冒号
      cout.put(':');

      /* 下面的步骤和上面的一样 */
      PyObject* py_str_value = PyObject_Str(value);
      if (py_str_value) {
            wchar_t* wc_str_value = PyUnicode_AsWideCharString(py_str_value, NULL);
            if (wc_str_value) {
                //输出值
                wcout << wc_str_value << endl;;
                PyMem_Free(wc_str_value);
            } else {
                cerr << "#dict:" << endl;
            }
            Py_XDECREF(py_str_value);
      } else {
            cerr << "#dict:" << endl;
            continue;
      }
    }


    //Long类型对应的是Python中的整数
    return PyLong_FromSsize_t(dict_len);
}


/*
*遍历集合
*/
static PyObject* WalkingSequence_WalkingDownSet(
    PyObject* self,
    PyObject* args
) {

    //声明一个Python对象(集合)
    PyObject* myset;

    //dict是一个Python对象
    if (!PyArg_ParseTuple(args, "O", &myset))
      return NULL;

    //检查是否是一个字典
    if (!PySet_Check(myset))
      return NULL;

    //集合性质三要素之一:有限性
    Py_ssize_t set_len = PySet_Size(myset);

    /*
    //集合肯定是可迭代的啦....
    if (PyIter_Check(myset))
      return NULL;
    */

    //跟在python中用iter函数获取到一个迭代对象一样
    PyObject* myiterator = PyObject_GetIter(myset);

    PyObject* item;

    //开始迭代,就类似于python中的myiterator = iter(myset);for item in myinterator: print(item)
    while ((item = PyIter_Next(myiterator)) != NULL) {
      PyObject* py_str = PyObject_Str(item);
      if (py_str) {
            wchar_t* wc_str = PyUnicode_AsWideCharString(py_str, NULL);
            if (wc_str) {
                wcout << wc_str << endl;
                PyMem_Free(wc_str);
            } else {
                cerr << "#set:" << endl;
            }
            Py_XDECREF(py_str);
      } else {
            cerr << "#set:" << endl;
      }
      //注意这里!
      Py_XDECREF(item);
    }

    return PyLong_FromSsize_t(set_len);
}


//我这里直接用宏来解决啦,函数名太长,如果此函数没有文档默认为NULL
#define WALKINGDOWNSEQUENCE_METHOD(name) {#name, WalkingSequence_##name, METH_VARARGS, NULL}
//格式{函数名字符串, 函数指针, 参数形式, 此函数/方法的文档(通过help函数获取)字符串}
//例如:{"WalkingDownList", WalkingSequence_WalkingDownList, METH_VARARGS, "这里是函数文档哟"}
/* 定义Python函数与C函数的映射表 */
static PyMethodDef WalkingSequenceMethods[] = {
    /* 下面的第一条与 {"WalkingDownList", WalkingSequence_WalkingDownList, METH_VARARGS, NULL} 等效 */
    WALKINGDOWNSEQUENCE_METHOD(WalkingDownList),
    WALKINGDOWNSEQUENCE_METHOD(WalkingDownTuple),
    WALKINGDOWNSEQUENCE_METHOD(WalkingDownDict),
    WALKINGDOWNSEQUENCE_METHOD(WalkingDownSet),
    {NULL, NULL, 0, NULL} //用全空的结构体成员元素作为结尾,也可以直接写{NULL},简洁
};


//文档就是可以通过help函数获取的字符串

/* 定义模块描述表(Python3.x必须定义) */
static PyModuleDef WalkingSequenceModule = {
    PyModuleDef_HEAD_INIT,//这个是必须加的
    "WalkingSequence",      //模块的名字
    "Here\'s WalkingSequenceModule's document", //模块文档
    -1,//给-1默认,代表模块存在于全局表
    WalkingSequenceMethods, //模块的函数映射表
    NULL, NULL, NULL, NULL// 目前还不用对模块进行特殊处理,全部默认NULL
};


/* 这里非常关键,是导出C函数到Python的接口函数 */
/* 这里使用的是Python3.x的 c/api 函数导出接口格式,有别于Python2.x的 */
/* 接口函数格式 PyInit_+模块文件名 */
PyMODINIT_FUNC PyInit_WalkingSequence(void) {
    /* 创建模块并返回模块 */
    return PyModule_Create(&WalkingSequenceModule);
}


最后就是灰常鸡冻的编译测试啦。。。。

大家使用的编译器都不一样,大家可能使用的是MSVC、GCC、ICC等编译器,我使用的是VS2017的编译器(vc++19.16.27025.1),连接器(link 14.16.27025.1)都是微软的。有的小伙伴使用的是gcc。
大家编译时可能会找不到头文件Python.h、找不到静态链接库Python37.lib,gcc的话找不到libpython37.a等
这些文件都在python的安装目录,如果时集成开发环境,就把安装目录下的Include、libs目录包含到工程中。
手动命令行编译的小伙伴记得也把这些个目录添加到对应的编译命令可选项中,下面以gcc为例
这里现在编译的时C++代码

g++ WalkingSequence.cpp -o WalkingSequence.pyd -I"这里时Include" -L“这里时libs” -lpython37 -shared


最后得到WalkingSequence.pyd文件,集成环境中设置一下生成文件的后缀名,或者等编译出目标文件后再对其进行修改成*.pyd作为后缀名的.
比如Windows下WalkingSequence.dll改成WalkingSequence.pyd,Linux下WalkingSequence.so改成WalkingSequence.pyd,其他系统的参考自己系统的相关文档。


最后我们得到了WalkingSequence.pyd这个模块。我们就可以使用啦,要得注意的是,模块名不是忽略大小写的!!!!
要得使用这个模块就得使当前的运行目录与这个模块在同一个目录下,嫌麻烦的把这个模块拷贝到python的安装目录下的Lib或者DLLs目录中。


下面是导入并简单的测试:





完结撒花❀❀❀❀❀❀❀


我最近正好学了点,分享给大家。


其实这几乎是想要什么功能就查相应功能的API,说到底文档得多看一下下。因为官方的相对来说是比较准确的!


如果文章又哪里表述错啦,或者哪里讲错啦,或者有什么问题,大家在评论区一起讨论,一起进步!

说到Python,我不得不再提一下小甲鱼,嘻嘻~~~~

小甲鱼:Change the world by programming!

sniper9527 发表于 2018-12-21 16:59

{:1_921:}............................

半瓶酱油 发表于 2018-12-21 17:05

楼主总结的不错。谢谢分享

sdh522 发表于 2018-12-21 17:10

快乐小风 发表于 2018-12-21 17:16

确实是干货 , 不过懵逼的比较多(包括我){:1_908:}

吾爱支持 发表于 2018-12-21 17:22

感谢楼主分享……送上我的小心心……。

希望楼主多分享这样的好文章……

xd13607900814 发表于 2018-12-21 21:31

谢谢楼主的分享

lovingxiaobing 发表于 2018-12-22 13:51

补一个例子,模拟print输出序列,虽然我们可以通过调用PyObject_Str轻松的获取到输出某个对象输出的样子,但是为了联系我自己也写了个类似的,遍历输出每一个元素。

这是Python代码:


import PrintSequence as CPrintSequence

##判断是否是可迭代对象(这里不对str,bytes进行判断)
isSequence = lambda sqc:(isinstance(sqc, list) or\
                                               isinstance(sqc, tuple) or\
                                               isinstance(sqc, dict) or\
                                               isinstance(sqc, set))

def PyPrintSequence(sqc):
       
        left = ""
        right = ""
        isDict = False
       
        ##是列表
        if isinstance(sqc, list):
                left='['; right=']'
        if isinstance(sqc, tuple):
                left='('; right=')'
        if isinstance(sqc, dict) or isinstance(sqc, set):
                left='{'; right='}'; isDict=True if isinstance(sqc,dict) else False
       
        ##列表,元组
        if isinstance(sqc, list) or isinstance(sqc, tuple):
                print(left, end='')
                for index in range(len(sqc)):
                        v = sqc
                        ##是列表,元组,字典,集合中的一个类型,继续向里面迭代
                        if isSequence(v):
                                PyPrintSequence(v)
                                print(',', end='')
                        else:
                                #有可能v是字符串
                                StringSybmol = "\"" if isinstance(v,str) else ""
                                print(StringSybmol,v.__str__(),StringSybmol, end=',')
                #退一格再打印,因为多了个逗号
                print('\b', end=right)
               
                #结束本次递归
                return None
       
       
        ##字典,集合
        if isinstance(sqc, dict) or isinstance(sqc, set):
                print(left, end='')
                for k in sqc:
                        #先打印键名,(冒号的打印取决于当前的sqc是字典还是集合)
                        #集合中的元素有可能是isSequence提及的可迭代对象
                        v = k;
                        if isDict:
                                print('\''+k+'\'', end=':')
                                v = sqc
                        ##是列表,元组,字典,集合中的一个类型,继续向里面迭代
                        if isSequence(v):
                                PyPrintSequence(v)
                                print(',', end='')
                        else:
                                StringSybmol = "\"" if isinstance(v,str) else ""
                                print(StringSybmol,v.__str__(),StringSybmol, end=',')
                print('\b', end=right)
                return None
       
       
        ##不是isSequence提及的可迭代对象,直接调用其__str__魔法方法获得字符串对象
        print(sqc, end=',')


myIter = (520,,[],666)


print("系统提供的打印:\n", myIter)

print("咱们自己的打印:\n")
PyPrintSequence(myIter)

print('\n')

print("咱们自己的C模块:")
CPrintSequence.Print(myIter)

print(input())


上面的模块是由下面的代码编译出来的:

//filename: PrintSequence..cpp
#include <iostream>
#include "Python.h"

using namespace std;

#pragma comment(lib, "Python37.lib")

/* std c++11 */
/* 判断是不是list、tuple、dict、set中的一种 */
auto isSequence = \
[=](PyObject* sqc)->bool{
    return ((PyList_Check(sqc)) || \
            (PyTuple_Check(sqc))|| \
            (PyDict_Check(sqc)) || \
            (PySet_Check(sqc)));
};

bool PrintValue(PyObject* py_str, bool isString=false) {
    wchar_t* wc_str;
    char syssymbol = 0;

    Py_ssize_t tsize;
    wc_str = PyUnicode_AsWideCharString(py_str, &tsize);

    if (!wc_str)
      return false;
    if (isString)
      syssymbol='\"';

    wcout << syssymbol << wc_str << syssymbol;

    return true;

}

static PyObject* RealPrintFunction(PyObject* sqc) {
    /* 左闭合符号和右闭合符号 */
    char left='\0', right='\0';

    /* 列表 */
    if (PyList_Check(sqc))
    { left='['; right=']'; }

    /* 元组 */
    if (PyTuple_Check(sqc))
    { left='('; right=')'; }

    /* 遍历列表或元组 */
    if (PyList_Check(sqc) || PyTuple_Check(sqc)) {
      /* 先输出符号 */
      cout.put(left);
      //获取长度
      Py_ssize_t length = PyList_Check(sqc) ? PyList_Size(sqc) : PyTuple_Size(sqc);
      //循环遍历
      for (Py_ssize_t index=0; index < length; index++) {
            //获取元素
            PyObject* v =PyList_Check(sqc) ? PyList_GetItem(sqc, index) : PyTuple_GetItem(sqc, index);
            if (v) {
                if (isSequence(v)) {
                  //递归遍历
                  RealPrintFunction(v);
                  cout.put(',');
                } else {
                  PyObject* py_str = PyObject_Str(v);
                  if (py_str) {
                        PrintValue(py_str, PyUnicode_Check(v));
                        cout.put(',');
                  }
                  Py_XDECREF(py_str);
                }
            }
      }

      cout.put('\b');
      cout.put(right);

      Py_RETURN_NONE;
    }



    bool isDict=false;

    /* 利用逻辑短路和逻辑断路判断并且修改isDict的值 */
    /* 字典和集合 */
    if (PySet_Check(sqc) || (PyDict_Check(sqc) && (isDict=true))) {
      left='{'; right='}';

      cout.put(left);

      /* 获取迭代器 */
      PyObject* myiterator = PyObject_GetIter(sqc);

      if (myiterator) {
            PyObject* k;
            while (NULL != (k = PyIter_Next(myiterator))) {
                PyObject* v = k;

                PyObject* py_str = PyObject_Str(v);

                if (!py_str) {
                  Py_XDECREF(k);
                  continue;
                }

                /* 如果是字典就输出键和冒号 */
                if (isDict) {

                  if (!PrintValue(py_str, PyUnicode_Check(v))) {
                        Py_XDECREF(py_str);
                        continue;
                  }
                  cout.put(':');

                  v = PyDict_GetItem(sqc, k);

                  //此时键已经作废
                  Py_XDECREF(k);

                  if (!v)
                        continue;

                  py_str = PyObject_Str(v);

                  if (!py_str)
                        continue;
                }

                if (isSequence(v)) {
                  //递归遍历
                  RealPrintFunction(v);
                  cout.put(',');
                } else {
                  PrintValue(py_str, PyUnicode_Check(v));
                  Py_XDECREF(py_str);
                  cout.put(',');

                  /* 如果是字典那么之前k已经作废(因为当是集合时v=k) */
                  if (!isDict)
                        Py_XDECREF(v);
                }
            }
      }

      cout.put('\b');
      cout.put(right);
    }

    Py_RETURN_NONE;
}

static PyObject* PrintSequence_Print(PyObject*self, PyObject* args) {
    PyObject* real_iterator_object;

    /* 检查是否是对象 */
    if (!PyArg_ParseTuple(args, "O", &real_iterator_object))
      return NULL;

    /* 检查是否是可迭代对象 */
    if (PyIter_Check(real_iterator_object)) {
      return NULL;
    }

    /* 调用C函数 */
    PyObject* ret = RealPrintFunction(real_iterator_object);

    cout.put('\n');

    return ret;
}

static PyMethodDef PrintSequenceMethods[] = {
    {"Print", PrintSequence_Print, METH_VARARGS, "Walking down Sequence"},
    {NULL}
};

static PyModuleDef PrintSequenceModule = {
    PyModuleDef_HEAD_INIT,
    "PrintSequence",
    "It\'s magic function called \'Print\' in PrintSequence!",
    -1,
    PrintSequenceMethods,
    NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC PyInit_PrintSequence(void) {
    return PyModule_Create(&PrintSequenceModule);
}



这是输出结果(有点诡异):


λ python PrintSequence.py
系统提供的打印:
(520, , [], 666)
咱们自己的打印:

( 520 ,[ 1 , b'\x11\x12\x13' , 3.14 ," biu~~~ ",{ 1 , 2 , 3 , 6 },{'c': 123 ,'b':(" qwe "," zxvc "),'lambda': <function <lambda> at 0x00C8F8A0> }, 233 ],[[ 111 ]], 666 )

咱们自己的C模块:
(5 2 0   ,,[],6 6 6   )

麦米尔加弗德 发表于 2018-12-22 21:47

看不懂 但我知道顶就是了

onoffon 发表于 2018-12-23 09:03

入门就看小甲鱼:lol
页: [1] 2
查看完整版本: 用C/C++写Python的扩展模块