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脚本实现nfs服务安装配置,共享文件分发

    ##############################Deploy nfs######################## echo "start deploy nfs-server& ...

  2. CentOS 6下编译安装MySQL 5.6

    一:卸载旧版本 使用下面的命令检查是否安装有MySQL Server rpm -qa | grep mysql 有的话通过下面的命令来卸载掉 rpm -e mysql //普通删除模式 rpm -e ...

  3. MySQL数据库基础(MySQL5.7安装、配置)

      写在前面: MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品.MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQ ...

  4. 拥抱.NET Core系列:MemoryCache 缓存过期

    在上一篇"拥抱.NET Core系列:MemoryCache 初识"中我们基本了解了缓存的添加.删除.获取,那么今天我们来看看缓存的过期机制.这里和上篇一样将把"Micr ...

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

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

  6. Win10 部署 依赖 NET3.5 项目,报错 无法安装 NET3.5 ,该如何解决?

    下载 NetFx3.cab Cab 安装包 拷贝 NetFx3.cab 文件至 C:\Windows 目录 打开命令行窗口(管理员权限) 输入以下内容: dism /online /Enable-Fe ...

  7. SharedPreferences封装类

    最近一直在读马伟奇老师的简书,给人以不一样的感觉,接下来的时间会做做笔记,毕竟好东西变成自己的才有用 原文地址SharedPreferencesUtils 依赖 dependencies { comp ...

  8. UVA1601 状态搜索

    很有意思的一道题,就是迷宫问题的增强版.但是细节很多,有一个技巧就是把每个可以走的位置编号方便状态判重. AC代码: #include<cstdio> #include<cstrin ...

  9. soj3129: windy和水星 -- 水星数学家 2

    注意int的范围:-2147483648-2147483647 如果输入会出现溢出,这题应该就是卡的这里.其使用long long就不用考虑这个. 加深:如果输入的数是很多位,直接当做字符串处理即可. ...

  10. 在SpringBoot中添加Logback日志处理

    前言 SpringBoot项目中在官方文档中说明,默认已经依赖了一些日志框架.而其中推荐使用的就是Logback,所以这一次我将在我的模版中加入Logback日志的配置,说明一下,SpringBoot ...