Python Cookbook(第3版)中文版:15.14 传递Unicode字符串给C函数库
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的相关文档
Python Cookbook(第3版)中文版:15.14 传递Unicode字符串给C函数库的更多相关文章
- Python Cookbook(第3版)中文版:15.15 C字符串转换为Python字符串
15.15 C字符串转换为Python字符串¶ 问题¶ 怎样将C中的字符串转换为Python字节或一个字符串对象? 解决方案¶ C字符串使用一对 char * 和 int 来表示, 你需要决定字符串到 ...
- Python Cookbook(第3版)中文版:15.17 传递文件名给C扩展
15.17 传递文件名给C扩展¶ 问题¶ 你需要向C库函数传递文件名,但是需要确保文件名根据系统期望的文件名编码方式编码过. 解决方案¶ 写一个接受一个文件名为参数的扩展函数,如下这样: static ...
- Python Cookbook(第3版)中文版:15.18 传递已打开的文件给C扩展
15.18 传递已打开的文件给C扩展¶ 问题¶ 你在Python中有一个打开的文件对象,但是需要将它传给要使用这个文件的C扩展. 解决方案¶ 要将一个文件转换为一个整型的文件描述符,使用 PyFile ...
- Python Cookbook(第3版) 中文版 pdf完整版|网盘下载内附提取码
Python Cookbook(第3版)中文版介绍了Python应用在各个领域中的一些使用技巧和方法,其主题涵盖了数据结构和算法,字符串和文本,数字.日期和时间,迭代器和生成器,文件和I/O,数据编码 ...
- Python Cookbook(第3版)中文版:15.16 不确定编码格式的C字符串
15.16 不确定编码格式的C字符串¶ 问题¶ 你要在C和Python直接来回转换字符串,但是C中的编码格式并不确定. 例如,可能C中的数据期望是UTF-8,但是并没有强制它必须是. 你想编写代码来以 ...
- Python Cookbook(第3版)中文版:15.21 诊断分段错误
15.21 诊断分段错误¶ 问题¶ 解释器因为某个分段错误.总线错误.访问越界或其他致命错误而突然间奔溃. 你想获得Python堆栈信息,从而找出在发生错误的时候你的程序运行点. 解决方案¶ faul ...
- Python Cookbook(第3版)中文版:15.19 从C语言中读取类文件对象
15.19 从C语言中读取类文件对象¶ 问题¶ 你要写C扩展来读取来自任何Python类文件对象中的数据(比如普通文件.StringIO对象等). 解决方案¶ 要读取一个类文件对象的数据,你需要重复调 ...
- Python Cookbook(第3版)中文版:15.20 处理C语言中的可迭代对象
15.20 处理C语言中的可迭代对象¶ 问题¶ 你想写C扩展代码处理来自任何可迭代对象如列表.元组.文件或生成器中的元素. 解决方案¶ 下面是一个C扩展函数例子,演示了怎样处理可迭代对象中的元素: s ...
- python cookbook第三版学习笔记十九:未包装的函数添加参数
比如有下面如下的代码,每个函数都需要判断debug的是否为True,而默认的debug为False def a(x,debug=False): if debug: print('calling a') ...
随机推荐
- Material使用11 核心模块和共享模块、 如何使用@angular/material
1 创建项目 1.1 版本说明 1.2 创建模块 1.2.1 核心模块 该模块只加载一次,主要存放一些核心的组件及服务 ng g m core 1.2.1.1 创建一些核心组件 页眉组件:header ...
- file_put_contents写入文字换行
file_put_contents写入文字换行 注意要使用双引号 "\r\n"
- IntelliJ IDEA(八) :git的使用
项目管理离不开版本控制,目前主流版本控制工具大概就是SVN和Git,至于两者有啥区别这里就不详细介绍了,如果有不明白的可以上网查资料,后期如果有机会我再开篇栏目细说,而且现在市场上Git的使用率已经远 ...
- docker容器安装及使用技巧
关于docker前言 A)首先是关于虚拟化 虚拟化我们可以简单的理解为一种资源管理方式.有如下几种虚拟化的方式: 1.完全虚拟化:对底层硬件实现完全的虚拟.例如:Vmware Workstation ...
- 韩信点兵(hanxin)
相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排.五人一排.七人一排地变换队形,而他每次只掠一眼队伍的排尾就知道总人数了.输入包含多组数据,每组数据包含3个非负整数a,b,c,表 ...
- 1.3 fractions模块
数学世界中,浮点数还可以用分数形式展示,不可约简的分数形式往往更简洁直观. 问题来了,Python中如何输出不可约简的分数形式呢? 答案:用Fraction类来实现.这个类属于标准库的fracti ...
- 百度前端技术学院js任务三
任务地址:http://ife.baidu.com/course/detail/id/98 代码: <!DOCTYPE> <html> <head> <met ...
- cf B. Mishka and trip (数学)
题意 Mishka想要去一个国家旅行,这个国家共有个城市,城市通过道路形成一个环,即第i个城市和第个城市之间有一条道路,此外城市和之间有一条道路.这个城市中有个首中心城市,中心城市与每个城市(除了 ...
- react——一个todolist的demo
代码如下: function ToDoListHeader(props) { return <h1 className={props.className}>ToDoList</h1& ...
- ActiveMq笔记3-AMQ高可用性理论
单点的ActiveMQ作为企业应用无法满足高可用和集群的需求,所以ActiveMQ提供了master-slave.broker cluster等多种部署方式,但通过分析多种部署方式之后我认为需要将两种 ...