这里我们简单说一些背景,就是突然想写一点用C去写Python库的东西,下面的这些方法和用法大部分参考官方介绍
从参数中获取的情况一般有两种,一般是我们如果明确了传入类型就是字典,并且需要知道需要解析的key,那么可以采用如下的方式进行解析:
static PyObject *my_func(PyObject* self, PyObject* args, PyObject* keyds)
{
static char* kwlist[] = {"age","name","level",NULL};
int age;
char *name;
int level;
if(!PyArg_ParseTupleAndKeywords(args,keyds,"isi", kwlist, &age,&name,&level))
{
return NULL;
}
/* 其他的工作 */
}
其中kwlist是需要获取的字典中的key列表,但是这样的话,我们的参数就变成了三个,因此需要修改一下声明:
{
"my_func",
(PyCFunction)my_func,
METH_VARARGS | METH_KEYWORDS,
"my_func"
},
在函数绑定的时候进行了一次类型转换。
还有一种情况是,我们只是获取字典,用于后面的解析工作,也不确定字典中的keylist
,那么可以使用如下的方式来读:
PyObject *dict;
if(!PyArg_ParseTuple(args, "O!", &PyDict_Type,&dict))
//if(!PyArg_ParseTuple(args, "O", &dict))
{
return NULL;
}
两种方式均可以获取字典,其中O
和O!
的区别,后续再说。
我们知道Python中的dict的keys其实是一个list,同样,在C语言中也是一样的,我们通过下面这个函数获取keys_list:
PyAPI_FUNC(PyObject *) PyDict_Keys(PyObject *mp);
该函数的返回值的同样也是一个PyObject*
的类型,也就是我们常说的key_list
。如果想便利keys_list
中的key,这可以使用list的函数进行遍历,函数原型如下:
PyAPI_FUNC(PyObject *) PyList_GetItem(PyObject *, Py_ssize_t);
其中两个参数分别是list以及在list中的位置。简单一点的遍历的例子如下:
PyObject* py_keys = PyDict_Keys(dict);
for(int i =0;i< PyList_Size(py_keys);++i)
{
//do something
//...
}
接下来就是通过key获取字典中的值,通常会借用下面的函数
PyAPI_FUNC(PyObject *) PyDict_GetItem(PyObject *mp, PyObject *key);
PyAPI_FUNC(PyObject *) PyDict_GetItemString(PyObject *dp, const char *key);
通过函数原型很容易知道,第一个API是通过PyObject获取,第二个是通过字符串key获取。
因为C语言不同于Python的中一切皆为对象,这里需要判断一下val的类型,以及完成对应类型的转换:
Python2中是采用PyString_Check
函数进行甄别的,判断是否为字符串,通过PyString_AsString
函数完成从PyObject*
到char*
的转换。
在Python3中是使用PyUnicode_Check
进行是否为字符串的判断,通过PyUnicode_AsUTF8
可以完成从PyObject*
到char*
的转换。
在Python2中,有两个文件均提供了判断的函数:
1. intobject.h
提供了PyInt_Check(op)
2. longobject.h
提供了PyLong_Check(op)
那么肯定是两种转换的接口:
1. intobject.h
提供了PyAPI_FUNC(int) _PyInt_AsInt(PyObject *)
2. intobject.h
提供了PyAPI_FUNC(long) PyInt_AsLong(PyObject *)
3. longobject.h
提供了PyAPI_FUNC(long) PyLong_AsLong(PyObject *)
;
4. longobject.h
提供了 PyAPI_FUNC(PY_LONG_LONG) PyLong_AsLongLong(PyObject *)
简单的例子如下:
int val = PyInt_AsLong(py_val);
在Python3中则没有这么多的转换函数,已经没有PyInt这一些函数了,因此只有Python2中一半的函数:
PyLong_Check(op)
longobject.h:PyAPI_FUNC(long) PyLong_AsLong(PyObject *);
longobject.h:PyAPI_FUNC(unsigned long) PyLong_AsUnsignedLong(PyObject *)
同样的,在Python只能够字典的value也可以是字典,因此可以通过PyDict_Check
来判断这个值得类型是不是字典。从而进行更深入的解析。
下面是一个简单的把dict读入到一个buffer中例子,其实也可以构建一个cpp中的类似Python的字典的类型。
static int dict2str(PyObject* dict , char* buffer, int buf_size)
{
int n_pos = 0;
int buf_cost_len = 0;
int buf_unused_len = buf_size;
if(dict == NULL)
{
//printf("the dict is null\n");
return -1;
}
if(NULL == buffer || buf_size <=0)
{
//printf("the buffer is null\n");
return -1;
}
PyObject* py_keys = PyDict_Keys(dict);
/* debug */
#define DEBUG_UINT(val) \
do{ \
printf("[%s][%s][%d][%s][%u]\n",__FILE__,__FUNCTION__,__LINE__,#val,val); \
}while(0)
DEBUG_UINT(PyDict_Size(dict));
DEBUG_UINT(PyList_Size(py_keys));
DEBUG_UINT(PY_MAJOR_VERSION);
for(int i =0;i< PyList_Size(py_keys);++i)
{
PyObject* py_key = PyList_GetItem(py_keys,i);
PyObject* py_val = PyDict_GetItem(dict, py_key);
//还可以使用PyDict_GetItemString,此时key为char*
#if PY_MAJOR_VERSION == 2
if(!PyString_Check(py_key))
{
continue;
}
char *key = PyString_AsString(py_key);
if (PyString_Check(py_val)) {
char *val = PyString_AsString(py_val);
n_pos = snprintf(buffer+buf_cost_len ,buf_unused_len,"key[%s],val[%s]|",key,val);
buf_unused_len -= n_pos;
buf_cost_len += n_pos;
continue;
}
if (PyInt_Check(py_val)) {
int val = PyInt_AsLong(py_val);
n_pos = snprintf(buffer+buf_cost_len ,buf_unused_len,"key[%s],val[%d]|",key,val);
buf_unused_len -= n_pos;
buf_cost_len += n_pos;
continue;
}
if (PyDict_Check(py_val)) {
//TODO: go on;
n_pos = snprintf(buffer+buf_cost_len ,buf_unused_len,"key[%s],val[%d]|",key,"{");
buf_unused_len -= n_pos;
buf_cost_len += n_pos;
int ret = dict2str(buffer+buf_cost_len,py_val,buf_unused_len);
if(ret >0)
{
buf_unused_len -= ret;
buf_cost_len += ret;
}
n_pos = snprintf(buffer+buf_cost_len ,buf_unused_len,"}|");
buf_unused_len -= n_pos;
buf_cost_len += n_pos;
continue;
}
#else
if(!PyUnicode_Check(py_key))
{
//printf("Error:[%d]the key is PyUnicode_Check error\n",__LINE__);
continue;
}
char * key = PyUnicode_AsUTF8(py_key);
//printf("key[%s]\n",key);
if(PyUnicode_Check(py_val))
{
//string
char *val = PyUnicode_AsUTF8(py_val);
n_pos = snprintf(buffer+buf_cost_len ,buf_unused_len,"key[%s],val[%s]|",key,val);
buf_unused_len -= n_pos;
buf_cost_len += n_pos;
continue;
}
if (PyLong_Check(py_val)) {
/* 新版中我没有找到转换为int 的支持,基本上都是long */
long val = PyLong_AsLong(py_val);
n_pos = snprintf(buffer+buf_cost_len ,buf_unused_len,"key[%s],val[%ld]|",key,val);
buf_unused_len -= n_pos;
buf_cost_len += n_pos;
continue;
}
if (PyDict_Check(py_val)) {
//TODO: go on;
n_pos = snprintf(buffer+buf_cost_len ,buf_unused_len,"key[%s],val[%s",key,"{");
buf_unused_len -= n_pos;
buf_cost_len += n_pos;
int ret = dict2str(py_val,buffer+buf_cost_len,buf_unused_len);
if(ret >0)
{
buf_unused_len -= ret;
buf_cost_len += ret;
}
n_pos = snprintf(buffer+buf_cost_len ,buf_unused_len,"}]|");
buf_unused_len -= n_pos;
buf_cost_len += n_pos;
continue;
}
#endif
}
return buf_cost_len;
}
通过上面的一些例子,我们不难发现,其实,在Python中,完成以后返回给Python用户使用者的还是一个类的对象指针,同样我们可以轻松的通过Py_BuildValue
构建一个对象进行返回。
PyAPI_FUNC(PyObject *) Py_BuildValue(const char *fmt, ...)
其中fmt为格式,举个例子,如果要返回一个str的类型,可以如下:
return Py_BuildValue("s","OK");
当然我们还有更为简单,明确的:
return PyUnicode_FromString("ok");
不过,看起来Py_BuildValue
更为通用一些,那么可想而知,如果想返回一个字典,就可以如下使用:
PyObject* ret_str = Py_BuildValue("{s:s}","result",buffer);
return ret_str;
这里虽然比较简单,但是不可控的因素比较多,通常我可能更传统一点的使用下面的方式来返回一个字典:
PyObject* ret_dict = PyDict_New();
// return Py_BuildValue("{s:s,s:i,s:s}","result","ok","result_code",0,"return_string",buffer );
ret = PyDict_SetItemString(ret_dict, "result",Py_BuildValue("s","ok"));
ret = PyDict_SetItemString(ret_dict, "result_code",Py_BuildValue("i",0));
ret = PyDict_SetItemString(ret_dict, "return_string",Py_BuildValue("s",buffer));
return ret_dict;
异曲同工,但是下面的更为灵活,不是么?至少不需要提前获知需要使用的类型都有什么。