CaptainChen

Python2 笔记—编码问题

2017-09-09 2012 words (about 9 min read) Views
Python

初学 Python2,感觉Python2中的编码问题一直很烦人,无意中看到几篇博文 [1] [2],感觉豁然开朗,做成笔记,方便日后查阅。

1. 从编码谈起

  • ASCII: 全称American Strandard Code for Information Interchange。ASCII只使用了一个字节(8个比特位)来编码。且ASCII只使用了后7位,最高位位0,因此只有128个字符编码。如字符A对应的二进制数值为01000001,对应的十进制数就是65。
  • EASCII(ISO/8859-1): ASCII扩展而来,主要是考虑西欧地区的符号编码。
  • GBK: 中文编码。开始是GB2312,又称GB0,共收录了6763个汉字,兼容ASCII。后来扩展到GBK,收录了27484个汉字,同时还收录了藏文等少数名族文字。GBK也是兼容ASCII,英文字符用1个字节表示,汉字用两个字节表示。
  • Unicode: Unicode全称Universal Multiple-Octet Coded Character Set, 又简称为UCS。UCS有两种格式:UCS-2和UCS-4,顾名思义就是用2个字节和4个字节来编码。Unicode只是规定了如何编码,没有规定如何传输,保存这个编码。比如一个汉字到底是用4个字节还是3个字节来表示,依赖于具体的实现方式,如UTF-8等。
  • UTF-8: Unicode Transformation Format,Unicode的一种实现方式,是可变长字符编码,根据具体使用情况用1-4个字节来表示一个字符。

2. 字节与字符,编码与解码

  • 字节与字符:

    • 字节:计算机存储一切数据都是由一串01的字节序列构成
    • 字符:就是一个符号,比如汉字,字母等
  • 编码与解码

    • 编码encode: 字符→字节。编码是为了存储和传输
    • 解码decode: 字节→字符。解码是为了显示和阅读

3. Python2的编码问题

3.1 默认编码

Python2默认用ASCII编码:

import sys
print sys.getdefaultencoding()  # ascii

就是说默认的解释器会把str类型的字符串当做ASCII编码来处理。

注意区分的是:文档开头往往加上一句# coding: utf-8是来指定脚本文件的编码方式。

3.2 str和unicode

Python2中,str和unicode都是basestring的子类,可见str和unicode是两种不同类型的字符串对象。

以汉字“禅”为例,str打印出来就是十六进制形式的\xe7\xa6\x85,对应于一长串二进制序列;而用unicode打印出来就是unicode符号u'\u7985':

  • str:
>>> s = '禅'
>>> s
'\xe7\xa6\x85'
>>> type(s)
<type 'str'>
  • unicode:
>>> u = u'禅'
>>> u
u'\u7985'
>>> type(u)
<type 'unicode'>

如果要把unicode符号保存到文件,或者是传输到网络,那就必须编码为str类型;反之亦然:

  • encode:
>>> u = u'禅'
>>> u
u'\u7985'
>>> u.encode('utf-8')
'\xe7\xa6\x85'
  • decode:
>>> s = '禅'
>>> s
'\xe7\xa6\x85'
>>> s.decode('utf-8')
u'\u7985'

说白了:

编码:字符到二进制数据的转换,即: encode: unicode → str

解码:二进制数据到字符的转换,即: decode: str → unicode

3.3 不同编码之间的转换

不同的编码之间通过unicode作为中间媒介来互相转换:

比如一个用utf-8编码好的汉字'\xe7\xa6\x85',要变成gbk:

'\xe7\xa6\x85'.decode('utf-8').encode('gbk')

3.4 UnicodeXXXError 错误及原因

  • case1:

    >>> s = '你好'
    >>> s.decode()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
    

    s.decode()默认使用ascii解码,但是ascii字符集中是没有中文字符的,因此报错: 0xe4超出范围了

  • case2:

    >>> a = u'你好'
    >>> a.encode()
    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)
    

    与case1类似,unicode转换str时默认用ascii,也是报错。

  • case3:

    >>> s = '你好'  # str类型
    >>> y = u'python'  # unicode类型
    >>> s + y    # 隐式转换,即 s.decode('ascii') + u
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
    

    str和unicode混用时,str会隐式地decode为unicode,然后也是由于用了ascii找不到汉字报错。这里详细见3.8节。

3.5 乱码问题

# coding: utf-8
>>> a='好'
>>> a
'\xe5\xa5\xbd'
>>> b=a.decode("utf-8")
>>> b
u'\u597d'
>>> c=b.encode("gbk")
>>> c
'\xba\xc3'
>>> print c
��

utf-8编码的字符‘好’占用3个字节,解码成Unicode后,如果再用gbk来解码后,只有2个字节的长度了,最后出现了乱码的问题,因此防止乱码的最好方式就是始终坚持使用同一种编码格式对字符进行编码和解码操作。

3.6 str()和unicode()

str()和unicode()是两个工厂方法,分别返回str字符串对象和unicode字符串对象。

其实本质为:

str(s) = s.encode('ascii')

unicode(s) = s.decode('ascii')

>>> s3 = u"你好"
>>> s3
u'\u4f60\u597d'
>>> str(s3)
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)
  • 技巧:

对于一个unicode形式的str字符串,如\u4f60\u597d这样的,如何变成真正的unicode呢?

最简单是前面加个u:

>>> print u'\u4f60\u597d'
你好

但是在像解析html时,储存在了一个字符串中,就可以这么做:

>>> s='\u4f60\u597d'
>>> type(s)
<type 'str'>
>>> s = s.decode('unicode-escape')
>>> print s
你好

3.7 检测编码

可用chardet检测字符编码:

>>> import chardet
>>> a = '好'
>>> print chardet.detect(a)
{'confidence': 0.73, 'language': '', 'encoding': 'ISO-8859-1'}

3.8 sys.setdefaultencoding('utf-8')的作用

Python2会在必要的情况下,对string作必要的编码类型转换,如==操作,字节和字符拼接,以及对str编码(encode)时。

看3.4的例子,我们这么操作就不会报错了:

>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>> sys.setdefaultencoding('utf-8')  # 初始化后删除了 sys.setdefaultencoding 方法,我们需要重新载入 
>>> s = '你好'
>>> y = u'python'
>>> s + y
u'\u4f60\u597dpython'

s和y类型不一样,于是Python调用更改后的默认编码utf-8对s进行decode为unicode再操作。

再看:

>>> s='你好'
>>> s.encode('gb2312')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

直接对str进行编码,首先会解码到unicode,默认ASCII,所以报错。

修改的方式为改成s.decode('utf-8').encode('gb2312')或者加上sys.setdefaultencoding('utf-8')

注意: 用这种方法是有潜在危害的,详见: 立即停止使用 setdefaultencoding('utf-8'), 以及为什么

文中提到,好的习惯是:

  • 所有 text string 都应该是 unicode 类型,而不是 str,如果你在操作 text,而类型却是 str,那就是在制造 bug。
  • 在需要转换的时候,显式转换。从字节解码成文本,用 var.decode(encoding),从文本编码成字节,用 var.encode(encoding)。
  • 从外部读取数据时,默认它是字节,然后 decode 成需要的文本;同样的,当需要向外部发送文本时,encode 成字节再发送。

3.9 print时的编码处理

Python2.7中调用print打印出var变量时,操作系统会对var做一定的字符处理:如果var是str类型的变量,则直接将var变量交付给终端进行显示;如果var变量是unicode类型,则操作系统首先将var编码成str类型的对象,再显示。
因此对于unicode中有中文,打印前一定要先encode('utf-8')之类,或者用sys.setdefaultencoding('utf-8')也行。


  1. Python 编码错误的本质原因 ↩︎

  2. 立即停止使用 setdefaultencoding('utf-8'), 以及为什么 ↩︎

如果你这里看到有广告,不妨点击一下,就是对本站最大的支持~



本文由 CaptainChen 创作
该文章采用 知识共享署名-非商业性使用 4.0 国际许可协议进行许可。转载请注明出处!
CopyRight © 2017 - 2020
本站已稳定运行 天 总访问量