在编写Python程序时经常会遇到乱码问题,如果搞不清楚编码问题就会晕头转向,如果搞清楚编码问题就会迎刃而解。
首先什么是字符编码,我们知道计算机中存储的数据都是0和1,显然不能把字符存进去,因此就把字符和数字对应起来,用数字代替字符存到存储介质中,而数字与字符的映射关系就是编码表。美国人最先制定了ASCII编码表,里面定义了字母、数字和其他一些常用字符的编码,编码数字范围为0~127。
但是问题来了,ASCII码表给英文用是足够了,但是没办法表示其他语言,所以针对不同的语言就产生了很多不同的编码,例如俄文编码集是KOI8、法文编码集是Latin1、中文的则是GBK,但是这样很不方便,尤其是假如在一份文档中包含几种语言的字符,这样就没法统一编解码了。
为了解决这一问题 Unicode 应运而生,Unicode编码集合了世界上的所有字符,这样就解决了编码不统一的问题,但是Unicode并不完美:它太浪费空间了,并且伴随着其他一些存储和传输上的缺陷。
基于此 UTF-8 出现了,它解决了 Unicode 的问题,而且像 Unicode 一样包含了所有字符,‘UTF’ 就是 ‘Unicode Transformation Format’ 的简写。目前 UTF-8 是世界最通用的编码格式。
在说 Python 中的编解码前先声明几个概念:
encoding: 由 Unicode 字符串转化为 字节串的过程叫 encoding
decoding: 由 字节串转化为 Unicode 字符的过程叫 decoding, 正好与 encoding 相反。
Unicode 编码称为抽象,而各个编码格式称为 Unicode 的实现,各个不同的编码实现都可以通过 Unicode 进行相互转换。
看懂这幅图中的关系就OK了。
下面进入正题说 Python 中的字符编码问题:
Py2 和 Py3 之间的编码做了很大的改动,先说Py3:
Py3 中的字符串 str 类型即为 Unicode, Unicode 经过编码得到的字节串即为 bytes, Py3 中的默认编码格式为 UTF-8, 而要编码为其他编码格式则要显示指定,也就是说:
In [1]: str1 = '嘿嘿'
In [2]: utf8_str = str1.encode()
In [3]: gbk_str = str1.encode(encoding='gbk')
In [4]: utf8_str
Out[4]: b'\xe5\x98\xbf\xe5\x98\xbf'
In [5]: gbk_str
Out[5]: b'\xba\xd9\xba\xd9'
In [6]:
解码要与当初编码的方式一致,否则就会报错或者乱码。
In [6]: utf8_str.decode()
Out[6]: '嘿嘿'
In [7]: gbk_str.decode('gbk')
Out[7]: '嘿嘿'
In [8]: gbk_str.decode()
---------------------------------------------------------------------------
UnicodeDecodeError Traceback (most recent call last)
<ipython-input-9-627ffed75053> in <module>()
----> 1 gbk_str.decode()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xba in position 0: invalid start byte
In [9]:
Py2的 str 就是字节串,在 str 字面量的前面加上 ‘u’ 就标示这是个 Unicode 串, 与 Python3 相反。Py2 源文件运行时对字符串字面量的编码方式默认为 ASCII 码,因此在 Py2 源文件中直接写中文会报错
str1 = '哈哈'
执行这个 python 文件会报错:SyntaxError: Non-ASCII character ‘\xe5’ in file py2_t.py on line 1, but no encoding declared
在 Py2 源文件的最开始加上 # -*- coding: <encoding name> -*-
,指定源代码中的字符串字面量的编码方式:
# -*- coding:utf-8 -*-
str1 = '哈哈'
print str1
但是在 Python2 中的 encoding、decoding 方法默认编码方式依然是 ASCII, 可以通过这个来修改程序的默认编码:
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
以上我们只是指定了程序运行内部的默认编码方式,在对文件进行读写时,就不只涉及程序的编码方式了。
在进行文件操作时,Py2 的字符串就是字节,读进来的是字节,写出去的也是字节,如果要写入一个 Unicode 串,则会用当前程序的默认编码方式编码后再写入,open 函数中的 mode 参数 加不加 ‘b’ 都是无所谓。
而 Py3 就不一样了,读写文件要区分 mode, 加 ‘b’ 的只能传 bytes, 不加 ‘b’ 的只能传 Unicode 串,加 ‘b’ 的情况很简单,就是直接读写字节串:
with open('yourfilename', 'rb') as f:
f.read() #得到bytes对象
with open('yourfilename', 'wb')as f:
f.write(data) #data必须是 bytes 对象
而如果是不加 ‘b’ 的情况,读写的数据必须是 Py3 字符串,而且会被编解码,根据当前的操作系统环境,而不是根据当前 python 程序的默认编码格式,如何查看当前操作系统的默认编码格式:
import locale
locale.getpreferredencoding()
默认 UNIX/Linux、Mac 是 UTF-8, windows 是gbk, 所以在Linux上运行的 python 程序读取一个 gbk 编码格式的文件,会报错:
#Linux 环境下运行
with open('yourfilename', 'r') as f:
f.read() #按 UTF-8 解码
所以读取不同编码格式的文件数据需要指定编码方式:
with open('yourfilename', 'r', encoding='gbk'):
f.read() #按 gbk 解码
同样,写入文件时如果不指定编码,会写入成功,但是文件可能会乱码。
Python3 对编码进行了改进,问题主要在 Python2, 要记住 Python2 中 str 是字节,u’hello’ 这种是 Unicode 串。任何编码方式的数据都可以通过 Unicode 进行转换。