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

字节问题

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章