容易撩倒人的Python字符编码问题

如果你正在使用 Python2.x ,那么你一定遇到了一些或者很多关于字符编码解码报错情况。是的,我遇到了不少,我决定写这篇文章,为了让自己加深对 python 的字符编码的理解。

相关概念

  • 计算机中的一切均为bytes(字节)。硬盘中的文件为一系列的byte组成,网络中传输的只有byte。所有的信息,在你写的程序中进进出出的,均由byte组成。
  • 字符:我们现在看到的英文字母、中文汉字就是经过计算机解码后对人类友好的抽象符号的表现
  • encode() 编码: 将字符转换成二进制流
  • decode() 解码: 将二进制流转换成字符

coding=utf-8和sys.getdefaultencoding()

如果我们的 py文件中包含中文,往往需要在第一行或者第二行添加:
# coding=utf-8 或者 # -*- coding: utf-8 -*-

这表示声明该 py文件中定义的字符串变量使用的编码方式为 utf-8。
python2.x 中,

>>> sys.getdefaultencoding()
'ascii'

python3.x 中,

>>> sys.getdefaultencoding()
'utf-8'

在使用 encode() 和 decode() 的时候,如果不传入任何参数,那么 python 解释器就会使用 sys.getdefaultencoding() 所指代的编码解码方式进行 encode() 和 decode()。

关于sys.stdout.encoding

在 Windows 系统下使用 python,特别要注意编码问题,因为我们一般都会使用 coding=utf-8 将 py文件中定义的字符串的编码方式设置为 UTF-8,而 Windows 系统下默认的编码方式为 GBK,即 sys.stdout.encoding 为 GBK(说明:微软的 CP936 不等于 GBK,它们有几十个不太常用的字符不同,所以绝大多数情况下感觉不到差异)。
如果我们不显式地将 bytes 编码成 unicode,然后再解码成 GBK的话,往往会出现乱码的情况,这一点需要特别注意。所以说,明确输出平台所采用的默认编码方式很重要,搞清楚了这一点解决 python 中文乱码的问题就容易多了。

Python2.x的字符编码问题

在 Python2.x 中,有三大类 string 类型,unicode(text string),str(byte string,二进制数据),basestring,是前两者的父类。具体分析如下:

  1. str 和 unicode 都是 basestring 的子类,不可被调用或实例化,仅可用于类型检查。isinstance(obj, basestring) 等价于 isinstance(obj, (str, unicode))。

    my_str = '我们'
    my_unicode = u'我们'
    
    >>> isinstance(my_str, basestring)
    True
    
    >>> isinstance(my_unicode, basestring)
    True
    
  2. str 等价于 bytes,是由unicode经过编码(encode)后的字节组成的字节串。在 python2.x 中你不加任何修饰直接定义的字符串,其实是字节串,切记这一点。

    >>> str == bytes
    True
    
    >>> '我们' == b'我们'
    True
    
    >>> b'我们' == str('我们')
    True
    
    >>> b'我们'
    '\xce\xd2\xc3\xc7'
    
    >>> len('我们')
    4
    #len('我们')在windows平台下长度为4(默认编码为GBK),Linux平台下长度为6(默认编码为UTF-8)。
    
  3. unicode 才是真正意义上的字符串,是由 str 解码(decode)后的字符组成的字符串。定义unicode 字符串,直接在 string 前面加前缀 u 即可。

    >>> len(u'我们')
    2
    
    >>> unicode('我们', 'utf-8')
    u'\u6211\u4eec'
    
    >>> '我们'.decode('utf-8')
    u'\u6211\u4eec'
    
    #无论是在Windows下还是在Linux下 u'我们' 的字符串长度都为2,这正是我们所想要的结果。
    
  4. 相互转换
    str –(decode)–> unicode –(encode)–> str

Python3.x 的字符编码问题

在 python3.x 中,字符编码问题就变得不那么混乱了,具体看下面:

  1. bytes 为字节串,如果你想定义一个字节串,不像在 python2.x 中那样直接定义就行了。在 python3.x 中,一般使用下面两种方式定义一个字节串:

    #第一种方法,加上 b 前缀
    >>> b'I am a byte'
    
    #第二种方法:
    >>> bytes(something, encodeing='xxx')
    

    如果想要定义一个含有中文的字节串,必须使用第二种方法,并且将 encoding参数设置为你为该 py文件设置的字符编码方式,即你在 py文件的头部设置的类似于 # coding: utf-8 中的编码方式。

  2. str,采用 unicode 方式编码的字符串。无论是 '我们' 还是 u'我们',都是str对象。

    >>> isinstance('我们',str)
    True
    
    >>> isinstance(u'我们',str)
    True
    
    >>> len('我们')
    2
    
  3. 相互转换
    bytes –(decode)–> str –(encode)–> bytes

总结与建议

  1. 在 python2.x 中,对两个字符串进行操作时,如果这两个字符串有一个是 unicode 编码,有一个是非 unicode 编码,python 会自动使用 sys.getdefaultencoding() 的解码方式将非 unicode 编码的字符串 decode 成 unicode 编码,再进行字符串操作,python2.x 悄悄掩盖掉了 byte 到 unicode 的转换,很容易出现问题。而在 python3.x 中,取消了 bytes 和 unicode 之间的自动隐性转换。
  2. 在需要转换的时候,全部显式转换。从字节解码成文本,用 your_string.decode(encoding),从文本编码成字节,用 your_string.encode(encoding)。
  3. 任何可能包含中文的字符串,请全部加上 u 前缀,这能减少很多问题。
  4. 从外部(网页、文件、数据库等)读取数据时,读取的是字节串,应该将其 decode 成 unicode进行使用;当需要向外部输出字符串时,用该外部媒介所能接收的编码形式 encode 字符串后再传递给它。

参考:

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器