15.14 传递Unicode字符串给C函数库

问题

你要写一个扩展模块,需要将一个Python字符串传递给C的某个库函数,但是这个函数不知道该怎么处理Unicode。

解决方案

这里我们需要考虑很多的问题,但是最主要的问题是现存的C函数库并不理解Python的原生Unicode表示。
因此,你的挑战是将Python字符串转换为一个能被C理解的形式。

为了演示的目的,下面有两个C函数,用来操作字符串数据并输出它来调试和测试。
一个使用形式为 char *, int 形式的字节,
而另一个使用形式为 wchar_t *, int 的宽字符形式:

void print_chars(char *s, int len) {
int n = 0; while (n < len) {
printf("%2x ", (unsigned char) s[n]);
n++;
}
printf("\n");
} void print_wchars(wchar_t *s, int len) {
int n = 0;
while (n < len) {
printf("%x ", s[n]);
n++;
}
printf("\n");
}

对于面向字节的函数 print_chars() ,你需要将Python字符串转换为一个合适的编码比如UTF-8.
下面是一个这样的扩展函数例子:

static PyObject *py_print_chars(PyObject *self, PyObject *args) {
char *s;
Py_ssize_t len; if (!PyArg_ParseTuple(args, "s#", &s, &len)) {
return NULL;
}
print_chars(s, len);
Py_RETURN_NONE;
}

对于那些需要处理机器本地 wchar_t 类型的库函数,你可以像下面这样编写扩展代码:

static PyObject *py_print_wchars(PyObject *self, PyObject *args) {
wchar_t *s;
Py_ssize_t len; if (!PyArg_ParseTuple(args, "u#", &s, &len)) {
return NULL;
}
print_wchars(s,len);
Py_RETURN_NONE;
}

下面是一个交互会话来演示这个函数是如何工作的:

>>> s = 'Spicy Jalape\u00f1o'
>>> print_chars(s)
53 70 69 63 79 20 4a 61 6c 61 70 65 c3 b1 6f
>>> print_wchars(s)
53 70 69 63 79 20 4a 61 6c 61 70 65 f1 6f
>>>

仔细观察这个面向字节的函数 print_chars() 是怎样接受UTF-8编码数据的,
以及 print_wchars() 是怎样接受Unicode编码值的

讨论

在继续本节之前,你应该首先学习你访问的C函数库的特征。
对于很多C函数库,通常传递字节而不是字符串会比较好些。要这样做,请使用如下的转换代码:

static PyObject *py_print_chars(PyObject *self, PyObject *args) {
char *s;
Py_ssize_t len; /* accepts bytes, bytearray, or other byte-like object */
if (!PyArg_ParseTuple(args, "y#", &s, &len)) {
return NULL;
}
print_chars(s, len);
Py_RETURN_NONE;
}

如果你仍然还是想要传递字符串,
你需要知道Python 3可使用一个合适的字符串表示,
它并不直接映射到使用标准类型 char *wchar_t * (更多细节参考PEP 393)的C函数库。
因此,要在C中表示这个字符串数据,一些转换还是必须要的。
PyArg_ParseTuple() 中使用”s#” 和”u#”格式化码可以安全的执行这样的转换。

不过这种转换有个缺点就是它可能会导致原始字符串对象的尺寸增大。
一旦转换过后,会有一个转换数据的复制附加到原始字符串对象上面,之后可以被重用。
你可以观察下这种效果:

>>> import sys
>>> s = 'Spicy Jalape\u00f1o'
>>> sys.getsizeof(s)
87
>>> print_chars(s)
53 70 69 63 79 20 4a 61 6c 61 70 65 c3 b1 6f
>>> sys.getsizeof(s)
103
>>> print_wchars(s)
53 70 69 63 79 20 4a 61 6c 61 70 65 f1 6f
>>> sys.getsizeof(s)
163
>>>

对于少量的字符串对象,可能没什么影响,
但是如果你需要在扩展中处理大量的文本,你可能想避免这个损耗了。
下面是一个修订版本可以避免这种内存损耗:

static PyObject *py_print_chars(PyObject *self, PyObject *args) {
PyObject *obj, *bytes;
char *s;
Py_ssize_t len; if (!PyArg_ParseTuple(args, "U", &obj)) {
return NULL;
}
bytes = PyUnicode_AsUTF8String(obj);
PyBytes_AsStringAndSize(bytes, &s, &len);
print_chars(s, len);
Py_DECREF(bytes);
Py_RETURN_NONE;
}

而对 wchar_t 的处理时想要避免内存损耗就更加难办了。
在内部,Python使用最高效的表示来存储字符串。
例如,只包含ASCII的字符串被存储为字节数组,
而包含范围从U+0000到U+FFFF的字符的字符串使用双字节表示。
由于对于数据的表示形式不是单一的,你不能将内部数组转换为 wchar_t * 然后期望它能正确的工作。
你应该创建一个 wchar_t 数组并向其中复制文本。
PyArg_ParseTuple() 的”u#”格式码可以帮助你高效的完成它(它将复制结果附加到字符串对象上)。

如果你想避免长时间内存损耗,你唯一的选择就是复制Unicode数据懂啊一个临时的数组,
将它传递给C函数,然后回收这个数组的内存。下面是一个可能的实现:

static PyObject *py_print_wchars(PyObject *self, PyObject *args) {
PyObject *obj;
wchar_t *s;
Py_ssize_t len; if (!PyArg_ParseTuple(args, "U", &obj)) {
return NULL;
}
if ((s = PyUnicode_AsWideCharString(obj, &len)) == NULL) {
return NULL;
}
print_wchars(s, len);
PyMem_Free(s);
Py_RETURN_NONE;
}

在这个实现中,PyUnicode_AsWideCharString() 创建一个临时的wchar_t缓冲并复制数据进去。
这个缓冲被传递给C然后被释放掉。
但是我写这本书的时候,这里可能有个bug,后面的Python问题页有介绍。

如果你知道C函数库需要的字节编码并不是UTF-8,
你可以强制Python使用扩展码来执行正确的转换,就像下面这样:

static PyObject *py_print_chars(PyObject *self, PyObject *args) {
char *s = 0;
int len;
if (!PyArg_ParseTuple(args, "es#", "encoding-name", &s, &len)) {
return NULL;
}
print_chars(s, len);
PyMem_Free(s);
Py_RETURN_NONE;
}

最后,如果你想直接处理Unicode字符串,下面的是例子,演示了底层操作访问:

static PyObject *py_print_wchars(PyObject *self, PyObject *args) {
PyObject *obj;
int n, len;
int kind;
void *data; if (!PyArg_ParseTuple(args, "U", &obj)) {
return NULL;
}
if (PyUnicode_READY(obj) < 0) {
return NULL;
} len = PyUnicode_GET_LENGTH(obj);
kind = PyUnicode_KIND(obj);
data = PyUnicode_DATA(obj); for (n = 0; n < len; n++) {
Py_UCS4 ch = PyUnicode_READ(kind, data, n);
printf("%x ", ch);
}
printf("\n");
Py_RETURN_NONE;
}

在这个代码中,PyUnicode_KIND()PyUnicode_DATA()
这两个宏和Unicode的可变宽度存储有关,这个在PEP 393中有描述。
kind 变量编码底层存储(8位、16位或32位)以及指向缓存的数据指针相关的信息。
在实际情况中,你并不需要知道任何跟这些值有关的东西,
只需要在提取字符的时候将它们传给 PyUnicode_READ() 宏。

还有最后几句:当从Python传递Unicode字符串给C的时候,你应该尽量简单点。
如果有UTF-8和宽字符两种选择,请选择UTF-8.
对UTF-8的支持更加普遍一些,也不容易犯错,解释器也能支持的更好些。
最后,确保你仔细阅读了 关于处理Unicode的相关文档

艾伯特(http://www.aibbt.com/)国内第一家人工智能门户

Python Cookbook(第3版)中文版:15.14 传递Unicode字符串给C函数库的更多相关文章

  1. Python Cookbook(第3版)中文版:15.15 C字符串转换为Python字符串

    15.15 C字符串转换为Python字符串¶ 问题¶ 怎样将C中的字符串转换为Python字节或一个字符串对象? 解决方案¶ C字符串使用一对 char * 和 int 来表示, 你需要决定字符串到 ...

  2. Python Cookbook(第3版)中文版:15.17 传递文件名给C扩展

    15.17 传递文件名给C扩展¶ 问题¶ 你需要向C库函数传递文件名,但是需要确保文件名根据系统期望的文件名编码方式编码过. 解决方案¶ 写一个接受一个文件名为参数的扩展函数,如下这样: static ...

  3. Python Cookbook(第3版)中文版:15.18 传递已打开的文件给C扩展

    15.18 传递已打开的文件给C扩展¶ 问题¶ 你在Python中有一个打开的文件对象,但是需要将它传给要使用这个文件的C扩展. 解决方案¶ 要将一个文件转换为一个整型的文件描述符,使用 PyFile ...

  4. Python Cookbook(第3版) 中文版 pdf完整版|网盘下载内附提取码

    Python Cookbook(第3版)中文版介绍了Python应用在各个领域中的一些使用技巧和方法,其主题涵盖了数据结构和算法,字符串和文本,数字.日期和时间,迭代器和生成器,文件和I/O,数据编码 ...

  5. Python Cookbook(第3版)中文版:15.16 不确定编码格式的C字符串

    15.16 不确定编码格式的C字符串¶ 问题¶ 你要在C和Python直接来回转换字符串,但是C中的编码格式并不确定. 例如,可能C中的数据期望是UTF-8,但是并没有强制它必须是. 你想编写代码来以 ...

  6. Python Cookbook(第3版)中文版:15.21 诊断分段错误

    15.21 诊断分段错误¶ 问题¶ 解释器因为某个分段错误.总线错误.访问越界或其他致命错误而突然间奔溃. 你想获得Python堆栈信息,从而找出在发生错误的时候你的程序运行点. 解决方案¶ faul ...

  7. Python Cookbook(第3版)中文版:15.19 从C语言中读取类文件对象

    15.19 从C语言中读取类文件对象¶ 问题¶ 你要写C扩展来读取来自任何Python类文件对象中的数据(比如普通文件.StringIO对象等). 解决方案¶ 要读取一个类文件对象的数据,你需要重复调 ...

  8. Python Cookbook(第3版)中文版:15.20 处理C语言中的可迭代对象

    15.20 处理C语言中的可迭代对象¶ 问题¶ 你想写C扩展代码处理来自任何可迭代对象如列表.元组.文件或生成器中的元素. 解决方案¶ 下面是一个C扩展函数例子,演示了怎样处理可迭代对象中的元素: s ...

  9. python cookbook第三版学习笔记十九:未包装的函数添加参数

    比如有下面如下的代码,每个函数都需要判断debug的是否为True,而默认的debug为False def a(x,debug=False): if debug: print('calling a') ...

随机推荐

  1. Maven下的SpringMVC MyBatis

    从头开始采用Maven管理,Spring.MyBatis.Tomcat. 在配置过程中SQL Server的Jar老是加载不了,解决方案参考前一篇博文. eclipse中已经自带了Maven的插件所以 ...

  2. Mac 上安装 GCC

    https://www.zhihu.com/question/20588567 安装 添加bin路径到$PATH变量

  3. 洛谷P2832 行路难 分析+题解代码【玄学最短路】

    洛谷P2832 行路难 分析+题解代码[玄学最短路] 题目背景: 小X来到了山区,领略山林之乐.在他乐以忘忧之时,他突然发现,开学迫在眉睫 题目描述: 山区有n座山.山之间有m条羊肠小道,每条连接两座 ...

  4. chrome浏览器下JavaScript实现clipboard时无法访问剪切板解决方案

    在用JavaScript实现某个简单的复制到剪切板功能的时候,会考虑一下浏览器兼容性,主要是重点在IE和FireFox,把这个两个浏览器搞定后,基本上其他浏览器也不用太操心了,Chrome也一样,没出 ...

  5. Log4j2配置文件详解

    目录[-] 1 系列目录 2 默认配置 3 第一个配置例子 4 复杂一点的配置 4.1 Appender之Syslog配置 4.2 Syslog及Syslog-ng相关配置(Fedora) 5 Log ...

  6. 在Vue2.0中集成UEditor 富文本编辑器

    在vue的'项目中遇到了需要使用富文本编辑器的需求,在github上看了很多vue封装的editor插件,很多对图片上传和视频上传的支持并不是很好,最终还是决定使用UEditor. 这类的文章网上有很 ...

  7. RotatedRect 类的用法

    RotatedRect 以 Emgu.CV.Structure 为命名空间. 表示带有旋转角度的矩形. 结构说明 普通矩形的基本结构

  8. POJ - 1733 Parity game 种类并查集+离散化

    思路:d(i, j)表示区间(i, j]的1的个数的奇偶性.输入最多共有5000*2个点,需要离散化处理一下.剩下的就是并查集判冲突. AC代码 #include <cstdio> #in ...

  9. UVA1602

    实现的细节很多,学到了如何翻转.旋转.平移,get很多技巧,值得一做. AC代码: #include<cstdio> #include<cstring> #include< ...

  10. uploadify上传文件(1)--下载

    最近在给公司做一个软件版本迭代管理的软件,是一个asp.net网站开发项目.利用mvc框架,前端采用bootstrap,数据库是MySQL,数据库访问利用EF框架. 软件需求是公司软件开发项目多,版本 ...