有许多理由给CPython写扩展,比如 1.性能低 2.重复用别人的C/C++代码 3.在自己的程序中定制python 4.为了方便 等等。

写这种扩展其实都是套路,不过最好要有对CPython源码有点熟悉。

添加C完成的 module有两种方法

1.直接修改python源代码,写好自己的module。静态编译到CPython中去.

2.将module做成dll(pyd)文件,然后动态加载。

套路

Python代码调用C代码套路:

  1. 将被调用C函数包装成一个进PyCFunction函数,
  2. 在这个函数包装函数中,拆开python传进来的参数
  3. 调用目标C函数

C调用python代码套路:

  1. 将Python要用到参数做成PyTupleObject,PyDictObject
  2. 调用PyObject_Call(callable,tuple,dict)

这个过程中要注意各种ref_count增减规则,可能会导致内存泄漏,一般规则似乎是,

  1. 在创建对象的时候,各种new函数会增加ref_cnt,
  2. 在传递参数的时候,被调函数不会负责额外ref_cnt,但是不需要额外的inc,dec。
  3. 在作为返回值的时候,如果在调用函数中,需要把这个返回值存储到某个地方的,那么inc一次。

整个原则就是在我们需要额外存储一下的时候,inc一次,在作为参数传递或者返回值,但是不会被赋值到其他地方的,那么就不需要inc了

一、添加module

myext module,将下面这份C代码编译成myext.dll(pyd),放到python.exe目录

#include <Python.h>

static PyObject *myextError;
static PyObject *callable;
//static PyObject *moduleself; static PyObject* myext_system(PyObject *self, PyObject *args);
static PyObject* myext_call(PyObject *self,PyObject *args,PyObject* kwargs); static PyMethodDef myextMethods[]={
{"system", myext_system, METH_VARARGS,"Execute a shell command."},
{"call",(PyCFunction)myext_call,METH_VARARGS|METH_KEYWORDS,"None"},
{NULL, NULL, 0, NULL}
}; //初始化module
PyMODINIT_FUNC initmyext(void)
{
PyObject *m;
//创建PyModuleObject对象,存储到PyInterpreterState.modules中去
m = Py_InitModule("myext", myextMethods);
if (m == NULL)
return;
//创建一个error class object
myextError = PyErr_NewException("myext.error", NULL, NULL);
Py_INCREF(myextError);
//将error 添加到m.dict
PyModule_AddObject(m, "error", myextError);
} /*
python代码调用C/C++代码,步骤
1.将被调用C函数包装成一个进PyCFunction函数,
2.在这个函数包装函数中,拆开python传进来的参数
3.调用目标C函数
*/
static PyObject* myext_system(PyObject *self, PyObject *args)
{
const char *command;
int sts; if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
if (sts < 0) {
PyErr_SetString(myextError, "System command failed");
return NULL;
} return Py_BuildValue("i",sts);//这种虽然可以使用,但是效率显然低,PyLong_FromLong
} /*
C代码调用Python代码,步骤
1.将Python要用到参数做成PyTupleObject,PyDictObject
2.调用PyObject_Call(callable,tuple,dict)
*/
static PyObject* myext_call(PyObject *self,PyObject* args,PyObject* kwargs){ if ((args!=NULL&&!PyTuple_Check(args))
||(kwargs!=NULL&&!PyDict_Check(kwargs)) ){
PyErr_SetString(PyExc_TypeError,"parameter type error");
return NULL;
} Py_ssize_t size=PyTuple_Size(args);
if (size<1){
PyErr_SetString(PyExc_Exception,"first parameter must be a callable object");
return NULL;
} PyObject* callablePositionArgs=NULL;
PyObject* callable=NULL; callable=PyTuple_GetItem(args,0);
if (!PyCallable_Check(callable)){
PyErr_SetString(PyExc_Exception,"first parameter must be a callable object");
return NULL;
} //切割tuple中剩余的值,作为 callable的参数
callablePositionArgs=PyTuple_GetSlice(args,1,size+1); return PyObject_Call(callable,callablePositionArgs,kwargs);
}

 

python测试代码:

import myext

def fun(a,b,kw1='abc',kw2="abc"):
print "a+b="+str(a+b),kw1,kw2 myext.call(fun,1,333,kw2='xyz') myext.system("ping www.weibo.com")
raw_input()

测试  

二、添加PyTypeObject

这个过程相当于给Python添加了新类型,我们从PyTypeObject开始。一个typeobject 就相当于一个类,通常(有列外id情况) Python vm根据这个typeobject的设置来创建 instance,设置一些规则函数和class特性flag,比如compare,iter,call,getset,hash之类的。不同的类有不同的状况,所以有不同的设置。

这个添加的过程感觉最复杂的地方还是Py_XINCREF和Py_XDECREF的部分,有点凌乱。

先解释几个PyTypeObject中的几个字段:

tp_new:这个要new出一个 Type对应的 instance对象来

student_init:这个相当于构造函数,现在越来越觉得构造函数应该叫初始化函数了,C++/C#/java中都应该这么称呼。

tp_methods,tp_members,tp_getset:这里的东西会转换设置到Type的dict/dictproxy中去

student_traverse:在gc.collect的时候回调,在这个函数中检测有可能出现循环引用的成员

student_clear:在gc发现有循环引用之后回调,这个函数的主要目的是用于断开循环引用

student_dealloc:在删除对象的时候回调

tp_basicsize:对象基本部分的大小

tp_itemsize:可变部分对象,每个item的大小。

tp_flags:Py_TPFLAGS_HAVE_GC这个标记在gc的时候才会用到,如果没这个标记应该是不会检测循环引用的。

另外有许多在PyType_Ready的时候会填上,它们大多是从object等等继承而来的。

举个新增类型的栗子:

#include "studenttype.h"

static PyObject* info(StudentObject* self){
return Py_None;
} static PyMemberDef Student_members[] = {
{"school", T_OBJECT_EX, offsetof(StudentObject, school), 0,"school"},
{"grade", T_INT, offsetof(StudentObject, grade), 0,"grade"},
{"name", T_OBJECT_EX, offsetof(StudentObject, name), 0,"name"},
//{"sex",T_OBJECT,offsetof(StudentObject,sex),0,"sex"},
{NULL} /* Sentinel */
}; static PyObject* student_new(PyTypeObject* typeobj,PyObject* args,PyObject* kwds){
StudentObject* self=NULL;
self=(StudentObject*)typeobj->tp_alloc(typeobj,0); if (self!=NULL){
Py_INCREF(Py_None);
self->grade=0;
} return (PyObject*)self;
} static void student_dealloc(StudentObject* student){
if (student!=NULL && student->ob_type==&PyStudent_Type){
printf("student dealloc\n");
Py_XDECREF(student->name);
Py_XDECREF(student->school);
Py_XDECREF(student->sex);
Py_CLEAR(student); if (student==NULL){
printf("removed\n");
}
}
} static int student_init(StudentObject* self, PyObject* args, PyObject* kwds){ PyObject *name=NULL,*school=NULL,*sex=NULL;
PyObject* tmp;
static char* kwlist[]={"grade","name","school","sex"};
if (!PyArg_ParseTupleAndKeywords(args,
kwds,
"|iSSS",
kwlist,
&self->grade,
&name,
&school,
&sex)){
return -1;
} if (name){
tmp=self->name;
Py_XINCREF(name);
self->name=name;
Py_XDECREF(tmp);
} if(school){
tmp=self->school;
Py_XINCREF(school);
self->school=school;
Py_XDECREF(tmp);
} if(sex){
tmp=self->sex;
Py_XINCREF(sex);
self->sex=sex;
Py_XDECREF(tmp);
} return 0;
} //gc回调函数
static int student_traverse(StudentObject *self, visitproc visit, void *arg)
{
if (self->ob_type==&PyStudent_Type){
printf("student_traverse\n");
Py_VISIT(self->name);
Py_VISIT(self->school);
Py_VISIT(self->sex);
} return 0;
} //在检测到有循环引用之后会调用这个函数
static int student_clear(StudentObject* student){
printf("student clear1\n");
Py_CLEAR(student->name);
Py_CLEAR(student->school);
Py_CLEAR(student->sex); return 0;
} static PyObject* getsex(StudentObject* self,void* closure){
printf("in getsex\n");
//Py_INCREF(self->sex);
return self->sex;
//Py_INCREF(Py_None);
//return Py_None;
} static int setsex(StudentObject* self,PyObject* value,void* closure){
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "are you joking?");
return -1;
} if (! PyString_Check(value)) {
PyErr_SetString(PyExc_TypeError, "the sex attribute must be string");
return -1;
} if (_PyString_Eq(value,PyString_FromString("male"))
|| _PyString_Eq(value,PyString_FromString("female"))){
Py_XDECREF(self->sex);
Py_XINCREF(value);
self->sex = value;
}else{
PyErr_SetString(PyExc_TypeError, "sex type should be female or male");
return -1;
} return 0;
} static PyGetSetDef Student_getseters[]={
{"sex",(getter)getsex,(setter)setsex, "about sex",NULL},
{NULL}
}; static PyMethodDef Student_methods[]={
{"info", (PyCFunction)info, METH_NOARGS,"Return detail info of student"},
{NULL} /* Sentinel */
}; static PyTypeObject PyStudent_Type = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"student", /*tp_name*/
sizeof(StudentObject), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)student_dealloc, /*tp_dealloc 这个 del student的时候会回调到*/
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|Py_TPFLAGS_HAVE_GC, /*tp_flags*/
"student", /* tp_doc */
(traverseproc)student_traverse, /* tp_traverse */
(inquiry)student_clear, /* tp_clear 不明白这个怎么回事*/
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Student_methods, /* tp_methods */
Student_members, /* tp_members */
Student_getseters, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)student_init, /* tp_init */
0, /* tp_alloc */
student_new, /* tp_new */
}; PyTypeObject* initstudenttype(){
//PyStudent_Type.tp_new=PyType_GenericNew;
if (PyType_Ready(&PyStudent_Type)){
return NULL;
}
return &PyStudent_Type;
}

python测试代码:

import myext
import gc
import sys from myext import student
s=student(grade=2,name='mike',school='thu',sex='male')
#s.name=s;
print("refcnt:",sys.getrefcount(s))
del s
gc.collect()

此处没有循环引用,结果

将s.name=s去掉注释,结果:

C扩展python的module和Type的更多相关文章

  1. 扩展Python模块系列(二)----一个简单的例子

    本节使用一个简单的例子引出Python C/C++ API的详细使用方法.针对的是CPython的解释器. 目标:创建一个Python内建模块test,提供一个功能函数distance, 计算空间中两 ...

  2. 2019-02-18 扩展Python控制台实现中文反馈信息之二-正则替换

    "中文编程"知乎专栏原文地址 续前文扩展Python控制台实现中文反馈信息, 实现了如下效果: >>> 学 Traceback (most recent call ...

  3. 使用c/c++扩展python

    用python脚本写应用比较方便,但有时候由于种种原因需要扩展python(比如给程序提供python接口等). 之前一直想整理下,今天终于坐下来把这件事情给做了,这里记录下,也方便我以后查阅. 说明 ...

  4. 使用C++扩展Python的功能 转自:http://blog.csdn.net/magictong/article/details/8897568#comments

    使用C++扩展Python的功能 环境 VS2005Python2.5.4 Windows7(32位) 简介 长话短说,这里说的扩展Python功能与直接用其它语言写一个动态链接库,然后让Python ...

  5. Requests:Python HTTP Module学习笔记(一)(转)

    Requests:Python HTTP Module学习笔记(一) 在学习用python写爬虫的时候用到了Requests这个Http网络库,这个库简单好用并且功能强大,完全可以代替python的标 ...

  6. C扩展Python

    基本想法: 先看中文小介绍,再看英文详细文档. 1. 参考 首先参考THIS, IBM的工程师好像出了好多这样的文章啊,而且每次看到时间戳,我都想戳自己- -! 2. ERROR 可能遇到错误: fa ...

  7. 扩展Python模块系列(五)----异常和错误处理

    在上一节中,讨论了在用C语言扩展Python模块时,应该如何处理无处不在的引用计数问题.重点关注的是在实现一个C Python的函数时,对于一个PyObject对象,何时调用Py_INCREF和Py_ ...

  8. python模块module package

    python模块module   package module package 常用模块 模块与包的区别 模块分为内置模块.第三方模块,自定义模块 程序会先从内置到第三方再到当前工作目录下去找你导入的 ...

  9. Python笔记 #01# Convert Python values into any type

    源:DataCamp datacamp 的 DAILY PRACTICE  + 日常收集. How much is your $100 worth after 7 years? Guess the t ...

随机推荐

  1. BitTorrent DHT 协议中文翻译

    前言 做了一个磁力链接和BT种子的搜索引擎 {Magnet & Torrent},因此把 DHT 协议重新看了一遍. BitTorrent 使用"分布式哈希表"(DHT)来 ...

  2. nginx、fastCGI、php-fpm关系梳理(转)

    前言: Linux下搭建nginx+php+memached(LPMN)的时候,nginx.conf中配需要配置fastCGI,php需要安装php-fpm扩展并启动php-fpm守护进程,nginx ...

  3. C++学习笔记 封装 继承 多态 重写 重载 重定义

    C++ 三大特性 封装,继承,多态 封装 定义:封装就是将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成类,其中数据和函数都是类的成员,目的在于将对 ...

  4. Nginx+uwsgi安装配置

    一.安装基础开发包 yum groupinstall "Development tools" yum install zlib-devel bzip2-devel pcre-dev ...

  5. css3的新特性transform,transition,animation

    一.transform css3引入了一些可以对网页元素进行变换的属性,比如旋转,缩放,移动,或者沿着水平或者垂直方向扭曲(斜切变换)等等.这些的基础都是transform属性 transform属性 ...

  6. ios 配置https

    一般来讲如果app用了web service , 我们需要防止数据嗅探来保证数据安全.通常的做法是用ssl来连接以防止数据抓包和嗅探 其实这么做的话还是不够的 . 我们还需要防止中间人攻击(不明白的自 ...

  7. 前端安全配置之Content-Security-Policy(csp)

    什么是CSP CSP全称Content Security Policy ,可以直接翻译为内容安全策略,说白了,就是为了页面内容安全而制定的一系列防护策略. 通过CSP所约束的的规责指定可信的内容来源( ...

  8. mongodb学习(一)

    操作系统环境:ubuntu. 安装mongodb:apt-get install mongodb 安装后运行:mongod提示:[initandlisten] exception in initAnd ...

  9. Oracle分页查询

    1.无ORDER BY排序的写法.(效率最高) 经过测试,此方法成本最低,只嵌套一层,速度最快!即使查询的数据量再大,也几乎不受影响,速度依然! sql语句如下: SELECT * FROM (Sel ...

  10. C#,C++修改vs文件模板,添加自定义代码版权版本信息

    简单型的修改类似该路径下的模板文件即可(vs版本或安装路径不同路径可能不同) C#: 模板参数参考https://msdn.microsoft.com/zh-cn/library/eehb4faa.a ...