Python中文本和字节序列的处理

    技术2026-02-13  29

    文本和字节序列

    字节问题字节概要基本的编解码器编码问题处理UnicodeEncodeError处理UnicodeDecodeError处理文本文件

    字节问题

    “字符串”是个相当简单的概念:一个字符串是一个字符序列。问题出 在“字符”的定义上。

    1.Unicode 标准把字符的标识和具体的字节表述进行了如下的明确区分 字符的标识,即码位,是 0~1 114 111 的数字(十进制),在Unicode 标准中以 4~6 个十六进制数字表示,而且加前缀“U+”。例 如,字母 A 的码位是 U+0041

    **2.字符的具体表述取决于所用的编码。编码是在码位和字节序列之间转换时使用的算法。**在 UTF-8 编码中,A(U+0041)的码位编码成 单个字节 \x41,而在 UTF-16LE 编码中编码成两个字节\x41\x00。再举个例子,欧元符号(U+20AC)在 UTF-8 编码中是三个字节——\xe2\x82\xac, 而在 UTF-16LE 中编码成两个字 节:\xac\x20。

    把码位转换成字节序列的过程是编码;把字节序列转换成码位的过程是解码。

    #编码和解码 >>> s = 'café' >>> len(s) # ➊ 4 >>> b = s.encode('utf8') # ➋ >>> b b'caf\xc3\xa9' # ➌ >>> len(b) # ➍ 5 >>> b.decode('utf8') # ➎ 'café'

    ➊ 'café’字符串有4个Unicode字符。 ➋ 使用UTF-8把str对象编码成bytes对象。 ➌ bytes字面量以b开头。 ➍ 字节序列b有5个字节(在UTF-8中,“é”的码位编码成两个字节)。 ➎ 使用UTF-8把bytes对象解码成str对象。

    总结:.decode() 和 .encode() 的区别,可以把字节序列想成晦涩难懂的机器磁芯转储,把 Unicode 字符串想 成“人类可读”的文本。那么,把字节序列变成人类可读的文本字符串就是解码,而把字符串变成用于存储或传输的字节序列就是编码。

    字节概要

    #包含5个字节的bytes和bytearray对象 >>> cafe = bytes('café', encoding='utf_8') #➊ >>> cafe b'caf\xc3\xa9' >>> cafe[0] #➋ 99 >>> cafe[:1] #➌ b'c' >>> cafe_arr = bytearray(cafe) >>> cafe_arr #➍ bytearray(b'caf\xc3\xa9') >>> cafe_arr[-1:] #➎ bytearray(b'\xa9')

    ➊bytes对象可以从str对象使用给定的编码创建。 ➋ 各个元素是range(256)内的整数。 ➌ bytes对象的切片还是bytes对象,即使是只有一个字节的切片。 ➍ bytearray对象没有字面量句法,而是以bytearray()和字节序列字面量参数的形式显示。 ➎ bytearray对象的切片还是bytearray对象。

    基本的编解码器

    #使用3个编解码器编码字符串“El Niño”,得到的字节序列差异很大 >>> for codec in ['latin_1', 'utf_8', 'utf_16']: print(codec, 'El Niño'.encode(codec), sep='\t') latin_1 b'El Ni\xf1o' utf_8 b'El Ni\xc3\xb1o' utf_16 b'\xff\xfeE\x00l\x00 \x00N\x00i\x00\xf1\x00o\x00'

    展示不同编解码器对“A”和高音谱号等字符编码后得到的字节序列

    latin1(即iso8859_1) 一种重要的编码,是其他编码的基础,例如cp1252和Unicode(注意,latin1与cp1252的字节值是一样的,甚至连码位也相同)。

    cp1252 Microsoft制定的latin1超集,添加了有用的符号,例如弯引号和€(欧元);有些Windows应用把它称为“ANSI”,但它并不是ANSI标准。

    cp437 IBM PC最初的字符集,包含框图符号。与后来出现的latin1不兼容。

    gb2312 用于编码简体中文的陈旧标准;这是亚洲语言中使用较广泛的多字节编码之一。

    utf-8 目前Web中最常见的8位编码;与ASCII兼容(纯ASCII文本是有效的UTF-8文本)。

    utf-16le UTF-16的16位编码方案的一种形式;所有UTF-16支持通过转义序列(称为“代理对”,surrogate pair)表示超过U+FFFF的码位。

    编码问题

    虽然有个一般性的UnicodeError异常,但是报告错误时几乎都会指明具体的异常: UnicodeEncodeError(把字符串转换成二进制序列时)或UnicodeDecodeError(把二进制序列转换成字符串时)。

    处理UnicodeEncodeError

    把文本转换成字节序列时,如果目标编码中没有定义某个字符,那就会抛出UnicodeEncodeError异常。

    #编码成字节序列:成功和错误处理 >>> city = 'São Paulo' >>> city.encode('utf_8') #➊ b'S\xc3\xa3o Paulo' >>> city.encode('utf_16') b'\xff\xfeS\x00\xe3\x00o\x00 \x00P\x00a\x00u\x00l\x00o\x00' >>> city.encode('iso8859_1') #➋ b'S\xe3o Paulo' >>> city.encode('cp437') #➌ Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/.../lib/python3.4/encodings/cp437.py", line 12, in encode return codecs.charmap_encode(input,errors,encoding_map) UnicodeEncodeError: 'charmap' codec can't encode character '\xe3' in position 1: character maps to <undefined> >>> city.encode('cp437', errors='ignore') #➍ b'So Paulo' >>> city.encode('cp437', errors='replace') #➎ b'S?o Paulo' >>> city.encode('cp437', errors='xmlcharrefreplace') #➏ b'S&#227;o Paulo'

    ➊ 'utf_?‘编码能处理任何字符串。 ➋ ‘iso8859_1’编码也能处理字符串’São Paulo’。 ➌ ‘cp437’无法编码’ã’(带波形符的“a”)。默认的错误处理方式’strict’抛出Unicode- EncodeError。 ➍ error=‘ignore’处理方式悄无声息地跳过无法编码的字符;这样做通常很是不妥。 ➎ 编码时指定error=‘replace’,把无法编码的字符替换成’?’;数据损坏了,但是用户知道出了问题。 ➏ 'xmlcharrefreplace’把无法编码的字符替换成XML实体。

    处理UnicodeDecodeError

    把二进制序列转换成文本时,如果假设是这两个编码中的一个,遇到无法转换的字节序列时会抛出UnicodeDecodeError。

    #把字节序列解码成字符串:成功和错误处理 >>> octets = b'Montr\xe9al' #➊ >>> octets.decode('cp1252') #➋ 'Montréal' >>> octets.decode('iso8859_7') #➌ 'Montrιal' >>> octets.decode('koi8_r') #➍ 'MontrИal' >>> octets.decode('utf_8') #➎ Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 5: invalid continuation byte >>> octets.decode('utf_8', errors='replace') #➏ 'Montr�al'

    ➊ 这些字节序列是使用latin1编码的“Montréal”;’\xe9’字节对应“é”。 ➋ 可以使用’cp1252’(Windows 1252)解码,因为它是latin1的有效超集。 ➌ ISO-8859-7用于编码希腊文,因此无法正确解释’\xe9’字节,而且没有抛出错误。 ➍ KOI8-R用于编码俄文;这里,’\xe9’表示西里尔字母“И”。 ➎ 'utf_8’编解码器检测到octets不是有效的UTF-8字符串,抛出UnicodeDecodeError。 ➏ 使用’replace’错误处理方式,\xe9替换成了“�”(码位是U+FFFD),这是官方指定的REPLACEMENT CHARACTER(替换字符),表示未知字符。

    处理文本文件

    处理文本的最佳实践是“Unicode三明治”

    >>> 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对象。 ⑪读取返回的字节序列,结果与预期相符。

    Processed: 0.012, SQL: 9