Python Cookbook(第3版)中文版:15.16 不确定编码格式的C字符串
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错误处理相关的信息。
Python Cookbook(第3版)中文版:15.16 不确定编码格式的C字符串的更多相关文章
- Python Cookbook(第3版) 中文版 pdf完整版|网盘下载内附提取码
Python Cookbook(第3版)中文版介绍了Python应用在各个领域中的一些使用技巧和方法,其主题涵盖了数据结构和算法,字符串和文本,数字.日期和时间,迭代器和生成器,文件和I/O,数据编码 ...
- Python Cookbook(第3版)中文版:15.14 传递Unicode字符串给C函数库
15.14 传递Unicode字符串给C函数库¶ 问题¶ 你要写一个扩展模块,需要将一个Python字符串传递给C的某个库函数,但是这个函数不知道该怎么处理Unicode. 解决方案¶ 这里我们需要考 ...
- 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版)中文版:15.19 从C语言中读取类文件对象
15.19 从C语言中读取类文件对象¶ 问题¶ 你要写C扩展来读取来自任何Python类文件对象中的数据(比如普通文件.StringIO对象等). 解决方案¶ 要读取一个类文件对象的数据,你需要重复调 ...
- Python Cookbook(第3版)中文版:15.20 处理C语言中的可迭代对象
15.20 处理C语言中的可迭代对象¶ 问题¶ 你想写C扩展代码处理来自任何可迭代对象如列表.元组.文件或生成器中的元素. 解决方案¶ 下面是一个C扩展函数例子,演示了怎样处理可迭代对象中的元素: s ...
- Python Cookbook(第3版)中文版:15.21 诊断分段错误
15.21 诊断分段错误¶ 问题¶ 解释器因为某个分段错误.总线错误.访问越界或其他致命错误而突然间奔溃. 你想获得Python堆栈信息,从而找出在发生错误的时候你的程序运行点. 解决方案¶ faul ...
- 实操一下<python cookbook>第三版1
这几天没写代码, 练一下代码. 找的书是<python cookbook>第三版的电子书. *这个操作符,运用得好,确实少很多代码,且清晰易懂. p = (4, 5) x, y = p p ...
随机推荐
- 深入JavaScript类型判定
JavaScript的数据类型 基本区分方法 ECMAScript标准定义了7种数据类型 6 种 基本类型: Boolean,两种取值:true和false Null,一种取值:null Undefi ...
- Windows Server 2016-WinSer2016 Active Directory新增功能
Windows Server 2016 Active Directory 域服务 (AD DS)新增很多功能用来提升Active Directory域及组织环境安全等,并帮助他们面向云的部署或混合部署 ...
- 基于Spring Boot,使用JPA动态调用Sql查询数据
在<基于Spring Boot,使用JPA操作Sql Server数据库完成CRUD>,<基于Spring Boot,使用JPA调用Sql Server数据库的存储过程并返回记录集合 ...
- C++函数返回值发生的对象复制
最近用QT做一个监控系统的项目,需要显示目标的运动轨迹,每次目标移动后,就在目标的轨迹中(用vector记录)添加一条新轨迹. 但是在运行中画面里一直不出现轨迹,经过调试发现是记录轨迹的函数出错了. ...
- php获取今日开始时间戳和结束时间戳
1.php获取今日开始时间戳和结束时间戳 $beginToday=mktime(0,0,0,date('m'),date('d'),date('Y'));$endToday=mktime(0,0,0 ...
- Dockerfile 中的 CMD 与 ENTRYPOINT
CMD 和 ENTRYPOINT 指令都是用来指定容器启动时运行的命令.单从功能上来看,这两个命令几乎是重复的.单独使用其中的一个就可以实现绝大多数的用例.但是既然 doker 同时提供了它们,为了在 ...
- 基于Parallax设计HTML视差效果
年关将至,给大家拜年. 最近时间充裕了一点,给大家介绍一个比较有意思的控件:Parallax.它可以用来实现鼠标移动时,页面上的元素也做偏移的视差效果.在一些有表现层次,布局空旷的页面上,用来做Hea ...
- 架构师入门:搭建双注册中心的高可用Eureka架构(基于项目实战)
本文的案例是基于 架构师入门:搭建基本的Eureka架构(从项目里抽取) 改写的. 在上文里,我们演示Eureka客户端调用服务的整个流程,在这部分里我们将在架构上有所改进.大家可以想象下,在上文里案 ...
- PowerDesigner Constraint name uniqueness 问题处理(转载)
使用PowerDesigner生成数据库脚本时报 Constraint name uniqueness 错误: 双击每行错误,发现外键引用的名字有重复的: 惯性去网上找解决办法,找到的主要是两个方法: ...
- cookie session区别
先理解为什么出现cookie和session: 为了使得网站可以跟踪客户端与服务器之间的交互,保存和记忆每个用户的身份和信息,我们需要一种强有力的解决方案,这样就产生了会话控制. 会话控制思想就是 ...