15.16 不确定编码格式的C字符串

问题

你要在C和Python直接来回转换字符串,但是C中的编码格式并不确定。
例如,可能C中的数据期望是UTF-8,但是并没有强制它必须是。
你想编写代码来以一种优雅的方式处理这些不合格数据,这样就不会让Python奔溃或者破坏进程中的字符串数据。

解决方案

下面是一些C的数据和一个函数来演示这个问题:

/* Some dubious string data (malformed UTF-8) */
const char *sdata = "Spicy Jalape\xc3\xb1o\xae";
int slen = 16; /* Output character data */
void print_chars(char *s, int len) {
int n = 0;
while (n < len) {
printf("%2x ", (unsigned char) s[n]);
n++;
}
printf("\n");
}

在这个代码中,字符串 sdata 包含了UTF-8和不合格数据。
不过,如果用户在C中调用 print_chars(sdata, slen) ,它缺能正常工作。
现在假设你想将 sdata 的内容转换为一个Python字符串。
进一步假设你在后面还想通过一个扩展将那个字符串传个 print_chars() 函数。
下面是一种用来保护原始数据的方法,就算它编码有问题。

/* Return the C string back to Python */
static PyObject *py_retstr(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(args, "")) {
return NULL;
}
return PyUnicode_Decode(sdata, slen, "utf-8", "surrogateescape");
} /* Wrapper for the print_chars() function */
static PyObject *py_print_chars(PyObject *self, PyObject *args) {
PyObject *obj, *bytes;
char *s = 0;
Py_ssize_t len; if (!PyArg_ParseTuple(args, "U", &obj)) {
return NULL;
} if ((bytes = PyUnicode_AsEncodedString(obj,"utf-8","surrogateescape"))
== NULL) {
return NULL;
}
PyBytes_AsStringAndSize(bytes, &s, &len);
print_chars(s, len);
Py_DECREF(bytes);
Py_RETURN_NONE;
}

如果你在Python中尝试这些函数,下面是运行效果:

>>> s = retstr()
>>> s
'Spicy Jalapeño\udcae'
>>> print_chars(s)
53 70 69 63 79 20 4a 61 6c 61 70 65 c3 b1 6f ae
>>>

仔细观察结果你会发现,不合格字符串被编码到一个Python字符串中,并且并没有产生错误,
并且当它被回传给C的时候,被转换为和之前原始C字符串一样的字节。

讨论

本节展示了在扩展模块中处理字符串时会配到的一个棘手又很恼火的问题。
也就是说,在扩展中的C字符串可能不会严格遵循Python所期望的Unicode编码/解码规则。
因此,很可能一些不合格C数据传递到Python中去。
一个很好的例子就是涉及到底层系统调用比如文件名这样的字符串。
例如,如果一个系统调用返回给解释器一个损坏的字符串,不能被正确解码的时候会怎样呢?

一般来讲,可以通过制定一些错误策略比如严格、忽略、替代或其他类似的来处理Unicode错误。
不过,这些策略的一个缺点是它们永久性破坏了原始字符串的内容。
例如,如果例子中的不合格数据使用这些策略之一解码,你会得到下面这样的结果:

>>> raw = b'Spicy Jalape\xc3\xb1o\xae'
>>> raw.decode('utf-8','ignore')
'Spicy Jalapeño'
>>> raw.decode('utf-8','replace')
'Spicy Jalapeño?'
>>>

surrogateescape 错误处理策略会将所有不可解码字节转化为一个代理对的低位字节(udcXX中XX是原始字节值)。
例如:

>>> raw.decode('utf-8','surrogateescape')
'Spicy Jalapeño\udcae'
>>>

单独的低位代理字符比如 \udcae 在Unicode中是非法的。
因此,这个字符串就是一个非法表示。
实际上,如果你将它传个一个执行输出的函数,你会得到一个错误:

>>> s = raw.decode('utf-8', 'surrogateescape')
>>> print(s)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'utf-8' codec can't encode character '\udcae'
in position 14: surrogates not allowed
>>>

然而,允许代理转换的关键点在于从C传给Python又回传给C的不合格字符串不会有任何数据丢失。
当这个字符串再次使用 surrogateescape 编码时,代理字符会转换回原始字节。例如:

>>> s
'Spicy Jalapeño\udcae'
>>> s.encode('utf-8','surrogateescape')
b'Spicy Jalape\xc3\xb1o\xae'
>>>

作为一般准则,最好避免代理编码——如果你正确的使用了编码,那么你的代码就值得信赖。
不过,有时候确实会出现你并不能控制数据编码并且你又不能忽略或替换坏数据,因为其他函数可能会用到它。
那么就可以使用本节的技术了。

最后一点要注意的是,Python中许多面向系统的函数,特别是和文件名、环境变量和命令行参数相关的
都会使用代理编码。例如,如果你使用像 os.listdir() 这样的函数,
传入一个包含了不可解码文件名的目录的话,它会返回一个代理转换后的字符串。
参考5.15的相关章节。

PEP 383
中有更多关于本机提到的以及和surrogateescape错误处理相关的信息。

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

Python Cookbook(第3版)中文版:15.16 不确定编码格式的C字符串的更多相关文章

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

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

  2. Python Cookbook(第3版)中文版:15.14 传递Unicode字符串给C函数库

    15.14 传递Unicode字符串给C函数库¶ 问题¶ 你要写一个扩展模块,需要将一个Python字符串传递给C的某个库函数,但是这个函数不知道该怎么处理Unicode. 解决方案¶ 这里我们需要考 ...

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

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

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

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

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

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

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

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

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

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

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

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

  9. 实操一下<python cookbook>第三版1

    这几天没写代码, 练一下代码. 找的书是<python cookbook>第三版的电子书. *这个操作符,运用得好,确实少很多代码,且清晰易懂. p = (4, 5) x, y = p p ...

随机推荐

  1. shell编程之环境变量配置文件(4)

    1 source命令 修改了配置文件,并让它立刻生效,而不用重新登录 source 配置文件 或者 .配置文件 2 环境变量配置文件简介 PATH HISTSIZE PS1 HOSTNAME等环境变量 ...

  2. 让linux 服务器网卡物理口不停闪烁

    [root@DBSERVER51 ~]# ethtool -p eth0 此时就会看到对应的物理口一个灯在不停的闪烁,对了.这就是我们在系统看到的那个叫eth0的网卡了.就是这么简单.

  3. 关于Apache配置虚拟主机后在局域网中让其他电脑访问

    #-----------adxssp------------# NameVirtualHost *:80 <VirtualHost *:80> ServerName www.b.com D ...

  4. 二分图最大匹配模板【匈牙利;Dinic最大流】

    二分图最大匹配模板[匈牙利:Dinic最大流] 匈牙利算法 int n,m; vector<int> map[100010]; int match[100010];//保存匹配的互相点 b ...

  5. 利用while语句,条件为输入的字符不为'\n'.

    题目:输入一行字符,分别统计出其中英文字母.空格.数字和其它字符的个数. 1.程序分析:利用while语句,条件为输入的字符不为'\n'. 一个很简单的问题,其实换种方式就能完成,但是我就想怎么着才能 ...

  6. C# decimal 去掉小数点后的无效0

    c#去掉小数点后的无效0 decimal d = 0.0500m; d.ToString("0.##")就出来了 也可以这样 string.Format("{0:0.## ...

  7. [原创]ubuntu14.04部署ELK+redis日志分析系统

    ubuntu14.04部署ELK+redis日志分析系统 [环境] host1:172.17.0.4 搭建ELK+redis服务 host2:172.17.0.3 搭建logstash+nginx服务 ...

  8. java程序员理解js中的闭包

    1.闭包概念: 就是函数内部通过某种方式访问一个函数内部的局部变量 再次理解: 闭包产生原因: 1.内部函数引用了外部函数的变量 作用:延长局部变量的生命周期 让函数外部可以调用到函数内部的数据 利用 ...

  9. 浅谈session,cookie,sessionStorage,localStorage的区别及应用场景

    浏览器的缓存机制提供了可以将用户数据存储在客户端上的方式,可以利用cookie,session等跟服务端进行数据交互. 一.cookie和session cookie和session都是用来跟踪浏览器 ...

  10. jdk 1.8 开发环境配置

    计算机->右键->属性->高级系统设置->环境变量->系统变量 新建系统变量:JAVA_HOME,变量值为:C:\Program Files (x86)\Java\jdk ...