python学习-4
[TOC]
4.1 字符问题
把码位转换成字节序列的过程是编码encode;把字节序列转换成码位的过程是解码decode。
1 |
|
4.2 字节概要
bytes 或 bytearray 对象的各个元素是介于 0~255(含)之间的整数,而不像 Python 2 的 str 对象那样是单个的字符。
1 |
|
虽然二进制序列其实是整数序列,但是它们的字面量表示法表明其中有 ASCII 文本。
因此,各个字节的值可能会使用下列三种不同的方式显示。
可打印的 ASCII 范围内的字节(从空格到 ~),使用 ASCII 字符本身。
制表符、换行符、回车符和 \ 对应的字节,使用转义序列 \t、\n、\r 和 \。
其他字节的值,使用十六进制转义序列(例如,\x00 是空字节)。
因此,我们看到的是 b’caf\xc3\xa9’:前 3 个字节 b’caf’ 在可打印的 ASCII 范围内,
**二进制序列有个类方法是 str 没有的,名为 fromhex,**它的作用是解 析十六进制数字对(数字对之间的空格是可选的),构建二进制序列
1 |
|
使用缓冲类对象构建二进制序列是一种低层操作,可能涉及类型转换。
1 |
|
结构体和内存视图
struct 模块提供了一些函数,把打包的字节序列转换成不同类型字段 组成的元组,还有一些函数用于执行反向转换,把元组转换成打包的字节序列。struct 模块能处理 bytes、bytearray 和 memoryview 对象。
这里用了PNG图了,知道意思就行
1 |
|
4.3 基本的编解码器
Python 自带了超过 100 种编解码器(codec, encoder/decoder),用于在文本和字节之间相互转换。每个编解码器都有一个名称,如 ‘utf_8’, 而且经常有几个别名,如 ‘utf8’、‘utf-8’ 和 ‘U8’。这些名称可以传给 open()、str.encode()、bytes.decode() 等函数的 encoding 参数。
1 |
|
4.4 了解编解码问题
多数非 UTF 编解码器只能处理 Unicode 字符的一小部分子集。把文本转 换成字节序列时,如果目标编码中没有定义某个字符,那就会抛出 UnicodeEncodeError 异常,除非把 errors 参数传给编码方法或函数,对错误进行特殊处理.
4.4.1 处理UnicodeEncodeError
1 |
|
4.4.2 处理UnicodeDecodeError
不是每一个字节都包含有效的 ASCII 字符,也不是每一个字符序列都是 有效的 UTF-8 或 UTF-16。因此,把二进制序列转换成文本时,如果假 设是这两个编码中的一个,遇到无法转换的字节序列时会抛出 UnicodeDecodeError。
1 |
|
4.4.3 使用预期之外的编码加载模块时抛出的 SyntaxError
Python 3 默认使用 UTF-8 编码源码,Python 2(从 2.5 开始)则默认使用 ASCII**。如果加载的 .py 模块中包含 UTF-8 之外的数据,而且没有声明编码,会得到类似下面的消息:**
1 |
|
解决,在文件顶部添加一个coding 注释
1 |
|
4.4.4 如何找出字节序列的编码(Chardet库)
统一字符编码侦测包 Chardet(https://pypi.python.org/pypi/chardet)就是 这样工作的,它能识别所支持的 30 种编码。Chardet 是一个 Python 库, 可以在程序中使用,不过它也提供了命令行工具 chardetect。
4.4.5 BOM:有用的鬼符
UTF-16 编码的序列开头有几个额外的字节,
1 |
|
在小字节序设备中,各个码位的最低有效字节在前面:字母 ‘E’ 的码位 是 U+0045(十进制数 69),在字节偏移的第 2 位和第 3 位编码为 69 和 0。
1 |
|
UTF-16 编码在要编码的文本前面加上特殊的不可见字 符 ZERO WIDTH NO-BREAK SPACE(U+FEFF)。在小字节序系统中, 这个字符编码为 b’\xff\xfe’(十进制数 255, 254)。
UTF-16 有两个变种:UTF-16LE,显式指明使用小字节序;UTF-16BE, 显式指明使用大字节序。如果使用这两个变种,不会生成 BOM:
1 |
|
如果有 BOM,UTF-16 编解码器会将其过滤掉,为你提供没有前导 ZERO WIDTH NO-BREAK SPACE 字符的真正文本。根据标准,如果文件 使用 UTF-16 编码,而且没有 BOM,那么应该假定它使用的是 UTF-16BE(大字节序)编码。然而,Intel x86 架构用的是小字节序,因此有 很多文件用的是不带 BOM 的小字节序 UTF-16 编码。 与字节序有关的问题只对一个字(word)占多个字节的编码(如 UTF-16 和 UTF-32)有影响。UTF-8 的一大优势是,不管设备使用哪种字节序,生成的字节序列始终一致,**因此不需要 BOM。**尽管如此,某些 Windows 应用(尤其是 Notepad)依然会在 UTF-8 编码的文件中添加 BOM;而且,Excel 会根据有没有 BOM 确定文件是不是 UTF-8 编码, 否则,它假设内容使用 Windows 代码页(codepage)编码。**UTF-8 编码 的 U+FEFF 字符是一个三字节序列:b’\xef\xbb\xbf’。**因此,如果文 件以这三个字节开头,有可能是带有 BOM 的 UTF-8 文件。然而, Python 不会因为文件以 b’\xef\xbb\xbf’ 开头就自动假定它是 UTF-8 编码的。
4.5 处理文本文件
处理文本的最佳实践是“Unicode 三明治”(如图 4-2 所示)。 意思是, 要尽早把输入(例如读取文件时)的字节序列解码成字符串。这种三明治中的“肉片”是程序的业务逻辑,在这里只能处理字符串对象。在其他处理过程中,一定不能编码或解码。对输出来说,则要尽量晚地把字符串编码成字节序列。
在 Python 3 中能轻松地采纳 Unicode 三明治的建议,因为内置的 open 函数会在读取文件时做必要的解码,以文本模式写入文件时还会做必要 的编码,所以调用 my_file.read() 方法得到的以及传给 my_file.write(text) 方法的都是字符串对象
1 |
|
写入文件时指定了 UTF-8 编码,但是读取文件时没有这么做, 因此 Python 假定要使用系统默认的编码(Windows 1252),于是文件的 最后一个字节解码成了字符 ‘é’,而不是 ‘é’。(我这vscode输出茅,输出的末尾多了变成了b’caf\xe8\x8c\x85’,也可能win11或者py3.9默认编码变了?后续测试是cp936编码)
仔细分析在 Windows 中运行的示例
1 |
|
使用encoding='utf_8’打开则是对的
有几个设置对 Python I/O 的编码默认值有影响
1 |
|
4.6 为了正确比较而规范化Unicode字符串
因为 Unicode 有组合字符(变音符号和附加到前一个字符上的记号,打 印时作为一个整体),所以字符串比较起来很复杂。 例如,“café”这个词可以使用两种方式构成,分别有 4 个和 5 个码位, 但是结果完全一样:
1 |
|
U+0301 是 COMBINING ACUTE ACCENT,加在“e”后面得到“é”。在 Unicode 标准中,‘é’ 和 ‘e\u0301’ 这样的序列叫“标准等价 物”(canonical equivalent),应用程序应该把它们视作相同的字符。但 是,Python 看到的是不同的码位序列,因此判定二者不相等。 这个问题的解决方案是使用 unicodedata.normalize 函数提供的 Unicode 规范化。这个函数的第一个参数是这 4 个字符串中的一 个:‘NFC’、‘NFD’、‘NFKC’ 和 ‘NFKD’。下面说明前两个。
NFC(Normalization Form C)使用最少的码位构成等价的字符串,而 NFD 把组合字符分解成基字符和单独的组合字符。这两种规范化方式都 能让比较行为符合预期:s1,s2同上
1 |
|
使用 NFC 时,有些单字符会被规范成另一个单字符。例如,电阻的单位欧姆(Ω)会被规范成希腊字母大写的欧米加。这两个字符在视觉上 是一样的,但是**比较时并不相等,因此要规范化,防止出现意外: ** 偷懒不敲了
**保存文本之前,最好使用 normalize(‘NFC’, user_text) 清洗字符串。**NFC 也是 W3C 的“Character Model for the World Wide Web: String Matching and Searching”规范
在 NFKC 和 NFKD 形式中,各个兼容字符会被替换成一个或多个“兼容 分解”字符,即便这样有些格式损失,但仍是“首选”表述——理想情况 下,格式化是外部标记的职责,不应该由 Unicode 处理。下面举个例 子。二分之一 ‘½’(U+00BD)经过兼容分解后得到的是三个字符序列 ‘1/2’;微符号 ‘µ’(U+00B5)经过兼容分解后得到的是小写字母 ‘μ’(U+03BC)。偷懒x2
4.6.1 大小写折叠
大小写折叠其实就是把所有文本变成小写,再做些其他转换。这个功能 由 str.casefold() 方法(Python 3.3 新增)支持。 对于只包含 latin1 字符的字符串 s,s.casefold() 得到的结果与 s.lower() 一样,唯有两个例外:微符号 ‘µ’ 会变成小写的希腊字 母“μ”(在多数字体中二者看起来一样);德语 Eszett(“sharp s”,ß) 会变成“ss”。
4.6.2 规范化文本匹配实用函数
由前文可知,NFC 和 NFD 可以放心使用,而且能合理比较 Unicode 字 符串。对大多数应用来说,NFC 是最好的规范化形式。不区分大小写的 比较应该使用 str.casefold()。 如果要处理多语言文本,工具箱中应该有示例 4-13 中的 nfc_equal 和 fold_equal 函数。
1 |
|
4.6.3 极端“规范化”:去掉变音符号(跳)
Google 搜索涉及很多技术,其中一个显然是忽略变音符号(如重音符、 下加符等),至少在某些情况下会这么做。去掉变音符号不是正确的规 范化方式,因为这往往会改变词的意思,而且可能误判搜索结果。但是 对现实生活却有所帮助:人们有时很懒,或者不知道怎么正确使用变音 符号,而且拼写规则会随时间变化,因此实际语言中的重音经常变来变 去。 除了搜索,去掉变音符号还能让 URL更易于阅读,至少对拉丁语系语 言是如此。下面是维基百科中介绍圣保罗市(São Paulo)的文章的 URL:
1 |
|
4.7 Unicode文本排序
Python 比较任何类型的序列时,会一一比较序列里的各个元素。对字符 串来说,比较的是码位。可是在比较非 ASCII 字符时,得到的结果不尽如人意。 下面对一个生长在巴西的水果的列表进行排序:
1 |
|
在 Python 中,非 ASCII 文本的标准排序方式是使用 locale.strxfrm 函数,根据 locale 模块的文档 (https://docs.python.org/3/library/locale.html? highlight=strxfrm#locale.strxfrm),这 个函数会“把字符串转换成适合所 在区域进行比较的形式”。
使用 locale.strxfrm 函数之前,必须先为应用设定合适的区域设置, 还要祈祷操作系统支持这项设置。在区域设为 pt_BR 的 GNU/Linux(Ubuntu 14.04)中,可以使用示例 4-19 中的命令。
1 |
|
使用Unicode排序算法排序
James Tauber,一位高产的 Django 贡献者,他一定是感受到了这一痛 点,因此开发了 PyUCA 库(https://pypi.python.org/pypi/pyuca/),这是 Unicode 排序算法(Unicode Collation Algorithm,UCA)的纯 Python 实现。
4.8 Unicode数据库
Unicode 标准提供了一个完整的数据库(许多格式化的文本文件),不 仅包括码位与字符名称之间的映射,还有各个字符的元数据,以及字符 之间的关系。例如,Unicode 数据库记录了字符是否可以打印、是不是 字母、是不是数字,或者是不是其他数值符号。字符串的 isidentifier、isprintable、isdecimal 和 isnumeric 等方法就 是靠这些信息作判断的。 str.casefold 方法也用到了 Unicode 表中的信息。 unicodedata 模块中有几个函数用于获取字符的元数据。例如,字符在标准中的官方名称是不是组合字符(如结合波形符构成的变音符号等), 以及符号对应的人类可读数值(不是码位)。
unicodedata.name() 和 unicodedata.numeric() 函数,以及字符串的 .isdecimal() 和 .isnumeric() 方法的用法:
1 |
|
4.9 支持字符串和字节序列的双模式API
标准库中的一些函数能接受字符串或字节序列为参数,然后根据类型展 现不同的行为。re 和 os 模块中就有这样的函数。
4.9.1 正则表达式中的字符串和字节序列
1 |
|
4.9.2 os函数中的字符串和字节序列(跳)
GNU/Linux 内核不理解 Unicode,因此你可能发现了,对任何合理的编 码方案来说,在文件名中使用字节序列都是无效的,无法解码成字符 串。在不同操作系统中使用各种客户端的文件服务器,在遇到这个问题 时尤其容易出错。 为了规避这个问题,os 模块中的所有函数、文件名或路径名参数既能 使用字符串,也能使用字节序列。如果这样的函数使用字符串参数调 用,该参数会使用 sys.getfilesystemencoding() 得到的编解码器 自动编码,然后操作系统会使用相同的编解码器解码。这几乎就是我们 想要的行为,与 Unicode 三明治最佳实践一致。 但是,如果必须处理(也可能是修正)那些无法使用上述方式自动处理 的文件名,可以把字节序列参数传给 os 模块中的函数,得到字节序列 返回值。这一特性允许我们处理任何文件名或路径名,不管里面有多少 鬼符
fsencode(filename)
如果 filename 是 str 类型(此外还可能是 bytes 类型),使用 sys.getfilesystemencoding() 返回的编解码器把 filename 编码成 字节序列;否则,返回未经修改的 filename 字节序列。
fsdecode(filename)
如果 filename 是 bytes 类型(此外还可能是 str 类型),使用 sys.getfilesystemencoding() 返回的编解码器把 filename 解码成 字符串;否则,返回未经修改的 filename 字符串。 在 Unix 衍生平台中,这些函数使用 surrogateescape 错误处理方式 (参见下述附注栏)以避免遇到意外字节序列时卡住。Windows 使用的 错误处理方式是 strict。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!,本博客仅用于交流学习,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。 文章作者拥有对此站文章的修改和解释权。如欲转载此站文章,需取得作者同意,且必须保证此文章的完整性,包括版权声明等全部内容。未经文章作者允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。若造成严重后果,本人将依法追究法律责任。 阅读本站文章则默认遵守此规则。