Python 文本和字节序列(处置惩罚文本文件)
本章将讨论下述话题:字符、码位和字节表述
bytes、bytearray 和 memoryview 等二进制序列的独特特性
全部 Unicode 和陈旧字符集的编解码器
制止和处置惩罚编码错误
处置惩罚文本文件的最佳实践
默认编码的陷阱和尺度 I/O 的题目
规范化 Unicode 文本,进行安全的比较
规范化、大小写折叠和暴力移除音调符号的实用函数
利用 locale 模块和 PyUCA 库正确地排序 Unicode 文本
Unicode 数据库中的字符元数据
能处置惩罚字符串和字节序列的双模式 API
处置惩罚文本文件
处置惩罚文本的最佳实践是“Unicode 三明治”(如图 4-2 所示)。 意思是,
要尽早把输入(比方读取文件时)的字节序列解码成字符串。这种三明
治中的“肉片”是步调的业务逻辑,在这里只能处置惩罚字符串对象。在其他
处置惩罚过程中,肯定不能编码或解码。对输出来说,则要只管晚地把字符
串编码成字节序列。多数 Web 框架都是如许做的,利用框架时很少接
触字节序列。比方,在 Django 中,视图应该输出 Unicode 字符串;
Django 会负责把响应编码成字节序列,而且默认利用 UTF-8 编码。
https://i-blog.csdnimg.cn/img_convert/04f90fdaeb67ea85a391f91f234aefe5.png
在 Python 3 中能轻松地采纳 Unicode 三明治的建议,因为内置的 open
函数会在读取文件时做必要的解码,以文本模式写入文件时还会做必要
的编码,所以调用 my_file.read() 方法得到的以及传给
my_file.write(text) 方法的都是字符串对象。
可以看出,处置惩罚文本文件很简单。但是,假如依赖默认编码,你会遇到
贫苦。
看一下示例 4-9 中的控制台会话。你能发现题目吗?
示例 4-9 一个平台上的编码题目(假如在你的机器上运行,它可
能会发生,也可能不会)
>>> open('cafe.txt', 'w', encoding='utf_8').write('café')
4 >>> open('cafe.txt').read()
'café'
题目是:写入文件时指定了 UTF-8 编码,但是读取文件时没有这么做,
因此 Python 假定要利用系统默认的编码(Windows 1252),于是文件的
最后一个字节解码成了字符 ‘é’,而不是 ‘é’。
我是在 Windows 7 中运行示例 4-9 的。在新版 GNU/Linux 或 Mac OS X
中运行同样的语句不会出题目,因为这几个操纵系统的默认编码是
UTF-8,让人误以为一切正常。假如打开文件是为了写入,但是没有指
定编码参数,会利用区域设置中的默认编码,而且利用那个编码也能正
确读取文件。但是,假如脚本要天生文件,而字节的内容取决于平台或
同一平台中的区域设置,那么就可能导致兼容题目。
需要在多台设备中或多种场合下运行的代码,肯定不能依赖
默认编码。打开文件时始终应该明确传入 encoding= 参数,因为
不同的设备利用的默认编码可能不同,有时隔一天也会发生变化。
示例 4-9 中有个希奇的细节:第一个语句中的 write 函数报告写入了 4
个字符,但是下一行读取时却得到了 5 个字符。示例 4-10 是对示例 4-9
的扩展,对这个题目以及其他细节做了阐明。
示例 4-10 仔细分析在 Windows 中运行的示例 4-9,找出并修正问
题
>>> fp = open('cafe.txt', 'w', encoding='utf_8')
>>> fp ➊
<_io.TextIOWrapper name='cafe.txt' mode='w' encoding='utf_8'>
>>> fp.write('café')
4 ➋
>>> fp.close()
>>> import os
>>> os.stat('cafe.txt').st_size
5 ➌
>>> fp2 = open('cafe.txt')
>>> fp2 ➍
<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='cp1252'>
>>> fp2.encoding ➎
'cp1252'
>>> fp2.read()
'café' ➏
>>> fp3 = open('cafe.txt', encoding='utf_8') ➐
>>> fp3
<_io.TextIOWrapper name='cafe.txt' mode='r' encoding='utf_8'>
>>> fp3.read()
'café' ➑
>>> fp4 = open('cafe.txt', 'rb') ➒
>>> fp4
<_io.BufferedReader name='cafe.txt'> ➓
>>> fp4.read() ⓫
b'caf\xc3\xa9'
❶ 默认情况下,open 函数接纳文本模式,返回一个 TextIOWrapper
对象。
❷ 在 TextIOWrapper 对象上调用 write 方法返回写入的 Unicode 字符
数。
❸ os.stat 报告文件中有 5 个字节;UTF-8 编码的 ‘é’ 占两个字节,
0xc3 和 0xa9。
❹ 打开文本文件时没有显式指定编码,返回一个 TextIOWrapper 对
象,编码是区域设置中的默认值。
❺ TextIOWrapper 对象有个 encoding 属性;查看它,发现这里的编
码是 cp1252。
❻ 在 Windows cp1252 编码中,0xc3 字节是“Ô(带波形符的 A),
0xa9 字节是版权符号。
❼ 利用正确的编码打开那个文件。
❽ 结果符合预期:得到的是四个 Unicode 字符 ‘café’。
❾ ‘rb’ 标记指明在二进制模式中读取文件。
❿ 返回的是 BufferedReader 对象,而不是 TextIOWrapper 对象。
⓫读取返回的字节序列,结果与预期符合。
除非想判定编码,否则不要在二进制模式中打开文本文件;
即便如此,也应该利用 Chardet,而不是重新发明轮子(参见 4.4.4
节)。通例代码只应该利用二进制模式打开二进制文件,如光栅图
像。
示例 4-10 的题目是,打开文本文件时依赖默认设置。默认设置有许多
来源,参见下一节。
编码默认值:一团糟
有几个设置对 Python I/O 的编码默认值有影响,如示例 4-11 中的
default_encodings.py 脚本所示。
示例 4-11 探索编码默认值
import sys, locale
expressions = """
locale.getpreferredencoding()
type(my_file)
my_file.encoding
sys.stdout.isatty()
sys.stdout.encoding
sys.stdin.isatty()
sys.stdin.encoding
sys.stderr.isatty()
sys.stderr.encoding
sys.getdefaultencoding()
sys.getfilesystemencoding()
"""
my_file = open('dummy', 'w')
for expression in expressions.split():
value = eval(expression)
print(expression.rjust(30), '->', repr(value))
示例 4-11 在 GNU/Linux(Ubuntu 14.04)和 OS X(Mavericks 10.9)中的
输出一样,表明这些系统中始终利用 UTF-8:
$ python3 default_encodings.py
locale.getpreferredencoding() -> 'UTF-8'
type(my_file) -> <class '_io.TextIOWrapper'>
my_file.encoding -> 'UTF-8'
sys.stdout.isatty() -> True
sys.stdout.encoding -> 'UTF-8'
sys.stdin.isatty() -> True
sys.stdin.encoding -> 'UTF-8'
sys.stderr.isatty() -> True
sys.stderr.encoding -> 'UTF-8'
sys.getdefaultencoding() -> 'utf-8'
sys.getfilesystemencoding() -> 'utf-8'
然而,在 Windows 中的输出有所不同,如示例 4-12 所示。
示例 4-12 在Windows 7(SP1)巴西版中的 cmd.exe 中输出的默认
编码;PowerShell 输出的结果雷同
Z:\>chcp ➊
Página de código ativa: 850
Z:\>python default_encodings.py ➋
locale.getpreferredencoding() -> 'cp1252' ➌
type(my_file) -> <class '_io.TextIOWrapper'>
my_file.encoding -> 'cp1252' ➍
sys.stdout.isatty() -> True ➎
sys.stdout.encoding -> 'cp850' ➏
sys.stdin.isatty() -> True
sys.stdin.encoding -> 'cp850'
sys.stderr.isatty() -> True
sys.stderr.encoding -> 'cp850'
sys.getdefaultencoding() -> 'utf-8'
sys.getfilesystemencoding() -> 'mbcs'
➊ chcp 输出当前控制台激活的代码页:850。
➋ 运行 default_encodings.py,把结果输出到控制台。
➌ locale.getpreferredencoding() 是最重要的设置。
➍ 文本文件默认利用 locale.getpreferredencoding()。
➎ 输出到控制台中,因此 sys.stdout.isatty() 返回 True。
➏ 因此,sys.stdout.encoding 与控制台的编码雷同。
假如把输出重定向到文件,如下所示:
Z:\>python default_encodings.py > encodings.log
sys.stdout.isatty() 的返回值会酿成
False,sys.stdout.encoding 会设为
locale.getpreferredencoding(),在那台设备中是 ‘cp1252’。
留意,示例 4-12 中有 4 种不同的编码。
假如打开文件时没有指定 encoding 参数,默认值由
locale.getpreferredencoding() 提供(在示例 4-12 中是
‘cp1252’)。
假如设定了 PYTHONIOENCODING 环境变量
(https://docs.python.org/3/using/cmdline.html#envvar-
PYTHONIOENCODING),sys.stdout/stdin/stderr 的编码使
用设定的值;否则,继续自所在的控制台;假如输入 / 输出重定向
到文件,则由 locale.getpreferredencoding() 界说。
Python 在二进制数据和字符串之间转换时,内部利用
sys.getdefaultencoding() 得到的编码;Python 3 很少如此,但
仍有发生。6 这个设置不能修改。
sys.getfilesystemencoding() 用于编解码文件名(不是文件内
容)。把字符串参数作为文件名传给 open() 函数时就会利用它;
假如传入的文件名参数是字节序列,那就不经改动直接传给 OS
API。“Unicode HOWTO”一文
(https://docs.python.org/3/howto/unicode.html)中说:“在 Windows
中,Python 利用 mbcs 这个名称引用当前配置的编码。”MBCS 是
Multi Byte Character Set(多字节字符集)的首字母缩写,在
Windows 中是陈旧的变长编码,如 gb2312 或 Shift_JIS,而不是
UTF-8。[ 关于这个话题,Stack Overflow 中有一个很好的答复“,
Difference between MBCS and UTF-8 on
Windows”(http://stackoverflow.com/questions/3298569/differencebetween-
mbcs-and-utf-8-on-windows)。]
在 GNU/Linux 和 OS X 中,这些编码的默认值都是 UTF-8,而
且多年来都是如此,因此 I/O 能处置惩罚所有 Unicode 字符。在
Windows 中,不仅同一个系统中利用不同的编码,另有只支持
ASCII 和 127 个额外的字符的代码页(如 ‘cp850’ 或
‘cp1252’),而且不同的代码页之间增加的字符也有所不同。因
此,若不多加小心,Windows 用户更容易遇到编码题目。
综上,locale.getpreferredencoding() 返回的编码是最重要的:这
是打开文件的默认编码,也是重定向到文件的
sys.stdout/stdin/stderr 的默认编码。然而,文档也说道(摘录部
分,https://docs.python.org/3/library/locale.html#locale.getpreferredencoding。
locale.getpreferredencoding(do_setlocale=True)
根据用户的偏好设置,返回文本数据的编码。用户的偏好设置在不
同系统中的设定方式不同,而且在某些系统中可能无法通过编程方
式设置,因此这个函数返回的只是猜测的编码……
因此,关于编码默认值的最佳建议是:别依赖默认值。
假如遵从 Unicode 三明治的建议,而且始终在步调中显式指定编码,那
将制止很多题目。痛惜,即使把字节序列正确地转换成字符串,
Unicode 仍有不尽如人意的地方。接下来的两节讨论的话题对 ASCII 世
界来说很简单,但是在 Unicode 范畴就变得相称复杂:文本规范化(即
为了比较而把文本转换成统一的表述)和排序。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]