字符/文本编码解码笔记
- 1.字符问题
- 编码和解码
- 2.字节概要
- 3.基本的编解码器
- 编码类型史
- 字符编码
- ASCII码
- GB2312以及其他编码
- UNICODE标准编码
- UTF-8编码
- 4.了解编解码问题
- 处理UnicodeEncoderError
- 解决方法:
- 处理UnicodeDecodeError
- 解决方法
- 5.修改源代码编码
- 6.查看文件编码方式
- 终端查看文件编码方式
- 代码内查看文件编码方式
- 7.处理文本文件
- 查看open函数默认编码方式
1.字符问题
“字符串”是个相当简单的概念:一个字符串是一个字符序列。
在 2015 年,“字符”的最佳定义是 Unicode 字符。因此,从 Python 3 的 str
对象中获取的元素是 Unicode 字符。但是Python 3 默认使用 UTF-8 编码源码。注意区分。
Unicode 标准把字符的标识和具体的字节表述进行了如下的明确区分。
- 字符的标识,即码位,是 0~1 114 111 的数字(十进制),在 Unicode 标准中以 4~6 个十六进制数字表示,而且加前缀“U+”。例如,字母 A 的码位是 U+0041。
- 字符的具体表述取决于所用的编码,也就是除了Unicode以外其他编码的表示。编码是在码位和字节序列之间转换时使用的算法。比如在 UTF-8 编码中,A(U+0041)的码位编码成单个字节 \x41。一个字符串则编码为由多个字节组成的字节序列。后面会多处提到字节序列,把它当成用其他编码器对Unicode字符编码的结果就可以了。
编码和解码
编码是把码位转换成字节序列的过程。通俗的讲就是将Unicode编码的字符转化为其他编码的字符。
解码是把字节序列转换成码位的过程。通俗的讲就是将其他编码的字符转化为Unicode编码的字符。
s='sabér'
print(s)
print(len(s)) #1
b=s.encode('utf-8') #2
print(b) #3
print(len(b)) #4
x=b.decode('utf-8')#5
print(x)
输出:
sabér
5
b'sab\xc3\xa9r'
6
sabér
- 1.
'sabér'
字符串有5个Unicode字符。 - 2.使用UTF-8把
str
对象编码成bytes
对象。 - 3.
bytes
字面量以b开头,表示字节序列。 - 4.字节序列 b 有 5 个字节(在 UTF-8 中,“é”的码位编码成两个字节)。
- 5.使用 UTF-8 把
bytes
对象(字节对象)解码成str
对象。
由于Unicode编码是标准编码格式,也可以看做是没有任何特定编码格式的“无编码”模式。所以,对于任何Unicode类型编码的字符,打印时python会自动根据环境编码转为特定编码后再显示,Python 3 为所有平台设置的默认编码都是 UTF-8。
2.字节概要
Python 内置了两种基本的二进制序列类型:不可变 bytes
类型和 可变 bytearray
类型。
3.基本的编解码器
Python 自带了超过 100 种编解码器(codec, encoder/decoder),用于在文本和字节之间相 互转换。每个编解码器都有一个名称,如 ‘utf_8’,而且经常有几个别名,如 ‘utf8’、‘utf-8’ 和 ‘U8’。
- 上图有12个字符,code point表示其码位,还有7种编码的字节表述(十六进制)。
- 星号表明,某些编码(如 ASCII 和多字节的 GB2312)不能表示所有 Unicode 字符。然而,UTF 编码的设计目的就是处理每一个 Unicode 码位。
编码类型史
字符编码
将人类语言转化为计算机能够理解的二进制的数(0,1),我们用bit(位)来表示,通常转化的时候是按byte(字节)来处理,一个字节等于8bit。
ASCII码
ASCII码是人类计算机历史上最早发明的字符集,ASCII码是专门为表示英文、数字以及英文标点符号而生。ASCII编码使用一个字节编码,一个字节有256种组合,足够存储这些字符。
GB2312以及其他编码
由于中文汉字非常多,ASCII无法满足汉字存储的需求。
为了满足国内在计算机中使用汉字的需要,中国国家标准总局发布了一系列的汉字字符集国家标准编码,统称为GB码,或国标码。
其中最有影响的是于1980年发布的《信息交换用汉字编码字符集 基本集》,标准号为GB 2312-1980,因其使用非常普遍,也常被通称为国标码。GB2312编码通行于我国内地;新加坡等地也采用此编码。几乎所有的中文系统和国际化的软件都支持GB 2312。
所以,大家可以理解为,GB系列的编码是为了适应复杂的中文编码而对ASCII码的一种扩充。
UNICODE标准编码
每个国家都会对ASCII码扩展出自己的一套编码,就存在不同编码之间的转换和显示问题。为了解决这个问题,需要一套统一的编码格式,即Unicode。
Unicode又被称为统一码、万国码;它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
Unicode编码通常由两个字节组成,共表示256*256个字符,即所谓的UCS-2。某些偏僻字还会用到四个字节,即所谓的UCS-4。
并且Unicode兼容ASCII,在Unicode中,原本ASCII中的127个字符只需在前面补一个全零的字节即可。
UTF-8编码
UNICODE编码有个缺点,所有语言的字符都是固定长度的存储,有些语言的字符只需占用少量的存储空间,这就导致浪费了大量的存储空间。
为了解决这个问题,就出现了一些中间格式的字符集,他们被称为通用转换格式,即UTF(Unicode Transformation Format)。
我们最常用的UTF-8就是这些转换格式中的一种。UTF-8编码其实是一种可“变长”的编码格式,即把英文变长为1个字节,而汉字用3个字节表示,特别生僻的还会变成4-6字节。
该部分参考:一文搞懂Python字符编码问题
4.了解编解码问题
虽然有个一般性的 UnicodeError 异常,但是报告错误时几乎都会指明具体的异常:UnicodeEncodeError(把字符串转换成二进制序列时)或 UnicodeDecodeError(把二进制序列转换成字符串时)。如果源码的编码与预期不符, 加载 Python 模块时还可能抛出 SyntaxError。
处理UnicodeEncoderError
多数非 UTF 编解码器只能处理 Unicode 字符的一小部分子集。把文本(Unicode 字符)转换成字节序列时,如果目标编码中没有定义某个字符,那就会抛出 UnicodeEncodeError 异常。
解决方法:
- 1.在encode()函数中添加参数error,举例如下:
city = 'São Paulo'
x=city.encode('cp437', errors='ignore')
print(x)
y=city.encode('cp437', errors='replace')
print(y)
z=city.encode('cp437', errors='xmlcharrefreplace')
print(z)
输出:
b'So Paulo'
b'S?o Paulo'
b'São Paulo'
error=‘ignore’ 处理方式悄无声息地跳过无法编码的字符;
error='replace’把无法编码的字符替换成 ‘?’;
error=‘xmlcharrefreplace’ 把无法编码的字符替换成 XML实体。
以上解决是十分不妥的,损坏了数据。
编解码器的错误处理方式是可扩展的。你可以为 errors 参数注册额外的字符串,方法是把一个名称和一个错误处理函数传给 codecs.register_error 函数。 参见 codecs.register_error 函数的官方文档。
- 2.换一种编码器,尽量使用UTF-8编码器。
处理UnicodeDecodeError
ASCII字符有127个,但是一个字节表示的空间有256个。所以不是每一个字节都包含有效的 ASCII 字符。
同理,不是每一个字符序列都是有效的 UTF-8 或 UTF-16。
把二进制序列(字节序列)转换成文本(Unicode字符)时,如果字节序列有无效的字节时,遇到无法转换的字节序列时会抛出 UnicodeDecodeError。
举例如下:
octets = b'Montr\xe9al'
oct=octets.decode('cp1252')
print(oct)
oct1=octets.decode('utf_8')
print(oct1)
oct2=octets.decode('utf_8', errors='replace')
print(oct2)
输出:
Montréal
Traceback (most recent call last):File "c:\Users\13451\Desktop\python基本操作\1.py", line 18, in <module>oct1=octets.decode('utf_8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 5: invalid continuation byte
'Montral'
- 这些字节序列是使用 latin1 编码的“Montréal”;’\xe9’ 字节对应“é”。
- 可以使用 ‘cp1252’(Windows 1252)解码,因为它是 latin1 的有效超集。
- ‘utf_8’ 编解码器检测到 octets 不是有效的 UTF-8 字符串,抛出 UnicodeDecodeError。bug中position从零开始数。
解决方法
使用 ‘replace’ 错误处理方式,\xe9 替换成了替换字符(码位是 U+FFFD),这是官方指定的 REPLACEMENT CHARACTER(替换字符),表示未知字符。替换字符:形状为黑色菱形,且中间填充了一个问号。
5.修改源代码编码
Python 3 默认使用 UTF-8 编码源码且Python 3 为所有平台设置的默认编码都是 UTF-8。
在源代码第一行加# coding: cp1252
即可用cp1252编码,或者编辑器或IDE都有更改编码的途径。
6.查看文件编码方式
终端查看文件编码方式
安装统一字符编码侦测包 Chardet。
终端使用:
chardetect 1.py
输出:
1.py: utf-8 with confidence 0.99
表示1.py有0.99的可能是utf-8编码。
代码内查看文件编码方式
使用chardet
库的detect
函数
import chardet
file1='data/test.txt'
f=open(file1,'rb')
f_encoding=chardet.detect(f.read())
print(f_encoding)
f.close()
输出:
{'encoding': 'utf-8', 'confidence': 0.99, 'language': ''}
7.处理文本文件
使用open()函数打开文件,有两种方式处理文件:文本模式和二进制模式。
添加encoding
参数可以设置编码方式,这个编码方式在编码输出文本的时候会用到,即下图第三行。open()函数默认采用文本模式。默认编码方式取决于平台,window10下为gbk,linux下为utf-8。当处于二进制模式时不能添加encoding参数,因为本身就是二进制了,不需要再编码。
- 如果调用read()函数,open 函数会在读取文件时做必要的解码(自动解码成Unicode字符,即str)。对应下图解码输入的字节序列,得到
str
对象。 - 如果调用write()函数,以文本模式写入文件时还会做必要的编码。参数为
str
对象。对应下图第三行,生成字节序列(bytes)。
整个处理文本文件的过程如下图:
查看open函数默认编码方式
file1='data/test.txt'
fp=open(file1,'w')
print(fp)
fp.close()
win10下输出:
<_io.TextIOWrapper name='data/test.txt' mode='w' encoding='cp936'>
cp936即 code page 936(代码页936)是以GBK(国标扩展字符集)为基础的编码。基本和GBK差不多。
关于编码默认值的最佳建议是:别依赖默认值。如果遵从 Unicode 三明治的建议,而且始终在程序中显式指定编码,那将避免很多问题。
参考了流畅的python第四章。