字符编码
unicode编码方式把所有语言统一成一套编码方式。一般使用两个字节表示一个字节,非常偏僻的就需要4个字节。目前大多数操作系统和编程语言都直接使用Unicode编码方式。
为了节省空间,一种‘可变长编码’的方式诞生了,他就是utf-8。utf-8编码把一个Unicode字符编码成1-6个字节,常用的字母为一个字节,汉字通常为3个字节,生僻字才会编码成4-6个字节。如果要传输大量英文字母的文本,两者相比utf-8就节省空间。
在计算机内存中,统一使用的是unicode,当需要保持到硬盘或者需要传输的时候,就会转化为utf-8。
用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里, 编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件:
浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器:
所以你看到很多网页的源码上会有类似的信息,表示该网页正是用的UTF-8编码
Python的字符串
python2与python3的是有差别的:
python2中表示unicode字符串需使用u'xxx',python3中都统一成unicode。而表示字节形式必须加上前缀b'xxx'。
由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输, 或者保存到磁盘上,就需要把str变为以字节为单位的bytes。
以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:
1 2 3 4 5 6 7 8 | >>> 'ABC'.encode('ascii') b'ABC' >>> '中文'.encode('utf-8') b'\xe4\xb8\xad\xe6\x96\x87' >>> '中文'.encode('ascii') Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128) |
纯英文的str可以用ASCII编码为bytes,内容是一样的,含有中文的str可以用UTF-8编码为bytes。含有中文的str无法用ASCII编码,因为中文编码的范围超过了ASCII编码的范围,Python会报错。
反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法:
1 2 3 4 | >>> b'ABC'.decode('ascii') 'ABC' >>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8') '中文' |
在操作字符串时,我们经常遇到str和bytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对str和bytes进行转换。
由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时, 就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:
1 2 | #!/usr/bin/env python # -*- coding: utf-8 -*- |
第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;
第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。
申明了UTF-8编码并不意味着你的.py文件就是UTF-8编码的,必须并且要确保文本编辑器正在使用UTF-8 without BOM编码:
如果.py文件本身使用UTF-8编码,并且也申明了# -- coding: utf-8 --,打开命令提示符测试就可以正常显示中文
Python2和3的差异
先准备两张图片
第一张图片是几个特殊字符的unicode代码点(CODE POINT), 用\uXXXX或者\xXX来表示,其中X都是十六进制字符,而\x表示前面一个字节为00就变成简写了。
第二张图片是UTF-8怎样来通过可变字节来编码相应字符的代码点
在python2中
-
str: a sequence of bytes
-
unicode: a sequence of code points
在 Python2 中,有两种字符串数据类型。一种纯旧式的文字: “str” 对象,存储 bytes 。如果你使用一个 “u” 前缀,那么你会有一个 “unicode” 对象,存储的是 code points 。在一个 unicode 字符串中,你可以使用\u或\x来插入任何的 unicode 代码点(\x后面接2位的十六进制表示\u00XX这样的简写)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | my_string = "Hello World." print(type(my_string)) my_unicdoe = u"Hi \u2119\u01b4\u2602\u210c\xf8\u1f24" print(len(my_unicode)) # 9个字符 my_utf8 = my_unicode.encode('utf-8') print(len(my_utf8)) # 19个字节 my_unicode2 = my_utf8.decode('utf-8') print(len(my_unicode2)) # 9个字符,恢复原来的 # my_unicode2.encode('ascii') # 报错,因为ascii只能表示0-127个字符 my_unicode2.encode('ascii', 'replace') # 用?代替不能编码的字符 my_unicode2.encode('ascii', 'xmlcharrefreplace') # 对于不能编码的产生一个完全替代的 HTML/XML 字符,保护所有的原始数据 my_unicode2.encode('ascii', 'ignore') # 忽略不能编码的字符,留下的都是ascii字符 |
在python3中
-
str: a sequence of code points
-
bytes: a sequence of bytes
-
只有一个文本类型是
str
(默认就是unicode编码字符) -
有两个字节类型是
bytes
和bytearray
在 Python 2 中的 “str” 现在叫做 “bytes”,而 Python 2 中的 “unicode” 现在叫做 “str”。
1 2 3 4 5 | my_string = "Hi \u2119\u01b4\u2602\u210c\xf8\u1f24" print(type(my_string)) # <class 'str'> my_bytes = b"Hello world" print(type(my_bytes)) # <class 'bytes'> |
避免bytes和unicode混合使用报错的原则是:制造一个 Unicode 三明治, bytes 在外, Unicode 在内。
我们有五个不可忽视的事实:
-
程序中所有的输入和输出均为 byte
-
世界上的文本需要比 256 更多的符号来表现
-
你的程序必须处理 byte 和 unicode
-
byte 流中不会包含编码信息
-
指明的编码有可能是错误的
常见乱码问题
-
SyntaxError: Non-ASCII character
-
UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xe5 in position 108
-
UnicodeEncodeError: ‘gb2312’ codec can’t encode character u’\u2014’ in position 72366:
字符串在Python内部的表示是unicode 编码,因此,在做编码转换时,通常需要以unicode作为中间编码, 即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。
decode的作用是将其他编码的字符串转换成unicode编码,如str1.decode(‘gb2312’),表示将gb2312编码的字符串str1转换成unicode编码。encode的作用是将unicode编码转换成其他编码的字符串,如str2.encode(‘gb2312’),表示将unicode编码的字符串str2转换成gb2312编码。
1 2 3 4 5 6 7 8 9 | #!/usr/bin/env python #coding=utf-8 s="中文" if isinstance(s, unicode): #s=u"中文" print s.encode('gb2312') else: #s="中文" print s.decode('utf-8').encode('gb2312') |
这是你在编程中保持 Unicode 清洁的三个建议:
-
Unicode 三明治:尽可能的让你程序处理的文本都为 Unicode 。
-
了解你的字符串。你应该知道你的程序中,哪些是 unicode, 哪些是 byte, 对于这些 byte 串,你应该知道,他们的编码是什么。
-
测试 Unicode 支持。使用一些奇怪的符号来测试你是否已经做到了以上几点。
如果你遵循以上建议的话,你将会写出对 Unicode 支持很好的代码。不管 Unicode 中有多么不规整的编码你的程序也不会挂掉。