概述
不管是哪种语言,大家应该都被字符集困扰过吧...对于Python开发而言,Python2和Python3的字符类型是不同的,Python3做了改变,所以理解起来可能更困难了。
正文部分:
要了解字符集,我们首先得知道在计算机的数据,所有的数据都是用二进制的形式进行存储的。而人们现实中使用的语言有很多,如:英语,中文和日语等等。那么把人类使用的语言翻译成计算机能懂的语言,就是字符集做的事情。
我们知道计算机是由英语国家发明的。所以最开始的时候,那帮老外发明了一种叫做ASCII的字符集。ASCII编码把128个英文的字符和符号做了编号映射到计算机的存储单元里(使用一个byte的后7个bits)。比如A 用65表示,a用97表示。由于只占用一个byte,所以ASCII用一个字节来存储一个字符。
但是随着计算机的使用国家增多,单纯的ASCII编码就满足不了其他使用非英语国家的需求了,比如中国和日本。为了解决这个问题,各个国家又分别推出了自己国家语言的字符集:中文(GBK,GB2312);日文(JIS).但是这样做显然是有问题的,想象一个场景,如果一个网页里既有中文又有日文还有英文,像上述的字符集就无法表示这些文字了,这也就是所谓的乱码。
为了解决上述问题,一个终极解决方案字符集诞生了,它就是:Unicode。这种编码对世界上大部分的文字系统进行了整理和编码,对于各个国家的各种字符提供了一个统一的数字进行表示,解决了字符集的问题。
UTF-8
Unicode编码范围是0~0x10FFFF,这么大的表示范围意味它无法用一个字节去存储,所以Unicode制定了各种存储编码格式,也就是我们常听到的UTF-8(Unicode转换格式:Unicode Transformation Format,UTF),UTF-16和UTF-32等等。这些数字代表的是Unicode转换格式把编码存储为一到多个单元。UTF-8的编码单位是8bit的字节,UTF-16是16位,UTF-32是32位。
实际中,我们最经常使用的编码方式是UTF-8。
Python字符集会出现的问题
了解了字符集,下面我们来看下Python的字符集会出现的问题。
In [33]: sys.version
Out[33]: '2.7.10 (default, Jul 15 2017, 17:16:57) \n[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)]'
In [34]: a = u'字符集'
Out[34]: u'\u5b57\u7b26\u96c6'
In [37]: print a
字符集
在Python2的版本,如果我们想要使用unicode编码,需要在字符前加一个u
。此时,如果想保存这个变量,可能会出现什么问题呢?
In [38]: with open('./test.txt', 'w') as f:
...: f.write(a)
...:
---------------------------------------------------------------------------
UnicodeEncodeError Traceback (most recent call last)
<ipython-input-38-3f33d2572bb0> in <module>()
1 with open('./test.txt', 'w') as f:
----> 2 f.write(a)
3
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)
可以看到,解释器报错,原因是它默认是存储ASCII编码的,但是ASCII显然无法存储汉字,所以抛出了异常。
那如果不用Unicode表示呢?
In [39]: a = '字符集'
In [40]: with open('./test.txt', 'w') as f:
...: f.write(a)
现在我们不用Unicode了,这样虽然可以直接存储,但是做一些切片操作时无法准确完成。
In [41]: a
Out[41]: '\xe5\xad\x97\xe7\xac\xa6\xe9\x9b\x86'
In [42]: print a[:2]
�
Python中的字符集
要解决上面的字符集问题,首先要记住Unicode是表现形式,UTF-8是存储形式。UTF-8解码变成Unicode,Unicode编码成为UTF-8(编码成二进制数据,原始的字节)。
然后需要记住两个版本的Python字符类型的差异:
Python3有两种字符表现类型:bytes和str。bytes指原始字节(8个二进制位),str指Unicode字符。
Python2有两种字符表现类型:str和Unicode。str指原始字节(8个二进制位),Unicode指的是Unicode字符。
在编写Python的过程中,一定要把编码和解码操作放到界面的最外围来做。程序的核心部分应该使用Unicode字符集(Python2中的Unicode,Python3中的str)。这样做的话,可以让程序接受多种类型的文本编码(GBK, JIS等),也可以保证输出文本信息只用一种编码形式(UTF-8)。
下面让我们来看两个函数:encode()和decode()。
贴一段代码来看下如何使用(Python2环境下):
In [50]: a
Out[50]: '\xe5\xad\x97\xe7\xac\xa6\xe9\x9b\x86'
In [51]: b = a.decode('utf-8')
In [52]: b
Out[52]: u'\u5b57\u7b26\u96c6'
In [53]: print b
字符集
In [54]: b.encode('utf-8')
Out[54]: '\xe5\xad\x97\xe7\xac\xa6\xe9\x9b\x86'
所以对于上面保存错误,我们可以这样来做:
In [39]: a = u'字符集'
In [40]: with open('./test.txt', 'w') as f:
...: f.write(a.encode('utf-8'))
而在程序编写中,我们要使用Unicode编码,就是说,如果读取这段数据,要进行解码:
In [61]: with open('./test.txt', 'r') as f:
...: data = f.read()
...:
In [62]: data.decode('utf-8')
Out[62]: u'\u5b57\u7b26\u96c6'
可以编写这样的函数,达到自动编码解码的目的:
Python3中,接收str或者bytes,但是总返回str,这个函数用于程序核心部分
def to_str(bytes_or_str):
if isinstance(bytes_or_str, bytes):
value = bytes_or_str.decode('utf-8')
else:
value = bytes_or_str
return value
存储时,用这个函数:
def to_bytes(bytes_or_str):
if isinstance(bytes_or_str, str):
value = bytes_or_str.encode('utf-8')
else:
value = bytes_or_str
return value
Python2中,对应的函数如下:
def to_unicode(unicode_or_str):
if isinstance(unicode_or_str, str):
value = unicode_or_str.decode('utf-8')
else
value = unicode_or_str
return value
和:
def to_str(unicode_or_str):
if isinstance(unicode_or_str, unicode):
value = unicode_or_str.encode('utf-8')
else
value = unicode_or_str
return value
Python3中需要注意的地方
Python3内置的open()函数获取文件的句柄时,会默认采用UTF-8编码格式来操作文件。而在Python2中,默认编码格式是二进制格式。这就会出现一些问题,比如说要向文件中写入二进制数据,在Python3中这样写就会出错:
In [12]: import sys
In [13]: sys.version
Out[13]: '3.6.3 (default, Oct 4 2017, 06:09:38) \n[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.37)]'
In [14]: import os
In [15]: with open('./rand.bin', 'w') as f:
...: f.write(os.urandom(10))
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-15-01652b9c0df8> in <module>()
1 with open('./rand.bin', 'w') as f:
----> 2 f.write(os.urandom(10))
3
TypeError: write() argument must be str, not bytes
发生异常的原因在于:python3给open函数添加了encoding这样一个新参数,这个参数的默认值是'utf-8',所以,开发需要传入str格式的数据,而不是bytes格式的数据。
为了解决这个问题,我们需要用二进制模式进行写入('wb'),读的时候类似,用('rb')进行读。