Python 字符编码详解

来源:互联网 发布:2016淘宝刷钻价格表 编辑:程序博客网 时间:2024/05/20 00:37

  接着之前写过的字符编码详解,这里我们来讲一讲“Python”中的字符编码问题。由于笔者使用的是Python2.7,而且较之于Python3,前者的字符编码问题显得更为复杂一些,因此本文所讲的“字符编码”基于Python2.7(以下简称“Python”)。


1.str和unicode

  Python中有两种字符串,str和unicode,它们都是basestring的子类。如下:

class basestring(object)class str(basestring)class unicode(basestring)

  basestring是str和unicode的超类(父类),也是抽象类,因此不能被调用和实例化,但可以被用来判断一个对象是否为str或者unicode的实例,isinstance(obj, basestring)等价于isinstance(obj, (str, unicode))。如下:

>>> my_str="你好">>> my_unicode=u"你好">>> my_str'\xe4\xbd\xa0\xe5\xa5\xbd'>>> my_unicodeu'\u4f60\u597d'>>> isinstance(my_str, str)True>>> isinstance(my_str, unicode)       False>>> isinstance(my_unicode, unicode)True>>> isinstance(my_unicode, str)       False>>> isinstance(my_str, basestring)       True>>> isinstance(my_unicode, basestring)   True

  严格来讲,str是字节字符串,由字符串的二进制字节组成(如果对str类型的字符串迭代的话,则会按照其在内存中的字节序依次迭代);unicode是文本字符串,由字符组成。如下:

>>> my_str="你好">>> my_unicode=u"你好">>> my_str'\xe4\xbd\xa0\xe5\xa5\xbd'>>> my_unicodeu'\u4f60\u597d'>>> len(my_str)#求的是字节数6>>> len(my_unicode)#求的是字符数2>>> my_str[1]'\xbd'>>> my_unicode[1]u'\u597d'

上例中,对于同一个中文字符串“你好”,str的表示为:'\xe4\xbd\xa0\xe5\xa5\xbd',这是字符串“你好”的utf8编码(e4 bd a0 e5 a5 bd),至于为什么不是GBK编码或者别的什么编码,则与环境设置有关;unicode的表示为:u'\u4f60\u597d',这是字符串“你好”的Unicode表示(U+4f60 U+597d)。有一点需要说明的是,Python中,用转义序列\xhh表示十六进制值为hh的字符,用转义序列\uxxxx表示十六进制值为xxxx的字符(仅限Unicode)。

  str和unicode,二者之间是可以相互转换的,依靠decode()和encode()两个方法即可完成转换:


这里写图片描述

来看一段代码:

>>> my_str="你好"    >>> my_unicode=u"你好">>> my_str'\xe4\xbd\xa0\xe5\xa5\xbd'>>> my_unicodeu'\u4f60\u597d'>>> my_str2unicode=my_str.decode("utf8")>>> my_unicode2str=my_unicode.encode("utf8")>>> my_str2unicodeu'\u4f60\u597d'>>> my_unicode2str'\xe4\xbd\xa0\xe5\xa5\xbd'

  当然,除了上图的标准转换方式外,我们还可以使用str.encode()和unicode.decode()方式进行转换,这就涉及到一个隐式转换的问题。当我们使用str.encode(encoding)时,实际上是在执行str.decode(sys.defaultencoding).encode(encoding);当我们使用unicode.decode(encoding)时,实际上是在执行unicode.encode(sys.defaultencoding).decode(encoding)。其中,defaultencoding的默认值为ASCII,如下:

>>> my_str="你好">>> my_str.encode("utf8")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)>>> import sys>>> sys.getdefaultencoding()'ascii'

显然,utf8编码的字符超出了ASCII的编码范围,所以抛出了异常(其中的codec是coder/decoder的首字母组合)。我们可以手动设置defaultencoding的值,使得decode()可以正确解码以执行str.encode(),如下:

>>> import sys>>> reload(sys)<module 'sys' (built-in)>>>> sys.setdefaultencoding("utf8")>>> sys.getdefaultencoding()      'utf8'>>> my_str="你好">>> my_str'\xe4\xbd\xa0\xe5\xa5\xbd'>>> my_str.encode("gbk")'\xc4\xe3\xba\xc3'

有心的读者可能已经注意到了,上述代码中似乎多加了一句reload(sys),这是因为,在sys加载后,setdefaultencoding函数被删除了,所以我们需要重新加载一遍。至于为什么要删掉,就是为了使用户无法在初始化后改变编码,以免出现一些未知的问题,针对这点,此处暂且按下不表。
  最后我们再提一下str()方法和unicode()方法。str(object)等同于object.encode(defaultencoding),unicode()则类似于decode(),具体如下:
  str()方法:

>>> my_unicode=u"你好">>> str(my_unicode)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)>>> import sys>>> sys.getdefaultencoding()'ascii'>>> reload(sys)<module 'sys' (built-in)>>>> sys.setdefaultencoding("utf8")>>> str(my_unicode)               '\xe4\xbd\xa0\xe5\xa5\xbd'

  unicode()方法:

>>> my_str="你好">>> unicode(my_str, "utf8")u'\u4f60\u597d'>>> unicode(my_str)        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)>>> import sys>>> reload(sys)<module 'sys' (built-in)>>>> sys.setdefaultencoding("utf8")>>> unicode(my_str)               u'\u4f60\u597d'

2.#coding:utf8

  我们经常能在Python脚本的第一行或者第二行看见类似于#coding:utf8的一行代码,这叫编码声明,它主要有以下作用:

  1. 根据代码文件中的编码声明,Python解析器可以对其中包含的特定编码字符作正确解析;
  2. 比较高级的编辑器,会根据编码声明,将此作为代码文件的存储格式。

  有一点需要注意的是,在my_str="中文"这样的语句中,字节串的编码方式取决于代码文件的存储格式,而非编码声明,所以编码声明和代码的存储格式要一致,否则Python解析器很可能因无法正确解析而报错。我们来看一个例子:

#coding:gbkmy_str="中文"

以上是Python脚本test_str.py,其存储格式为utf8,执行后会报错:

  File "test_str.py", line 2SyntaxError: 'gbk' codec can't decode bytes in position 10-11: illegal multibyte sequence

很明显,由于文件的存储格式为utf8,所以"中文"的编码为utf8,而文件的编码声明为gbk,所以Python解析器将以gbk的方式去解析"中文",二者不一致,所以抛出异常。

  另外,上一节出现了类似于my_unicode=u"中文"这样的语句,它的作用是完成str到unicode的转换,只要编码声明正确,Python便可以将str正确转换为unicode。

  关于编码声明,读者可以阅读PEP 263,这里我们只强调以下几点:

  1. 必须放在第一行或者第二行;
  2. 声明的通用格式:^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)或者# vim: set fileencoding=<encoding name> :
  3. 如果你的代码文件本身编码是带BOM的UTF-8,即文件前三个字节是\xef\xbb\xbf,那么:a)、即使你没有附加编码声明,也会当做是UTF-8的编码;b)、如果你声明了文件编码,则必须是UTF-8,否则会报错;

3.setdefaultencoding()

  第一节中,提及了sys模块中的setdefaultencoding()函数,这一节,我们准备对它做一个详述。
  当我们启动Python解释器的时候,会自动加载库文件site.py,其中有这么一段代码:

# Remove sys.setdefaultencoding() so that users cannot change the# encoding after initialization.  The test for presence is needed when# this module is run as a script, because this code is executed twice.if hasattr(sys, "setdefaultencoding"):    del sys.setdefaultencoding

  从这段代码可以看到,当我们import sys后,其中的setdefaultencoding()函数就会被删除,这也就解释了为什么要reload(sys)才能使用该函数。那么,为什么要删除setdefaultencoding()函数?这是因为,随意更改默认编码会引发一些意想不到的问题。来看两个例子:

#coding:utf8my_str="中文"my_unicode=u"中文"my_str==my_unicode
#coding:utf8import sysreload(sys)sys.setdefaultencoding("utf8")my_str="中文"my_unicode=u"中文"my_str==my_unicode

  执行第二段代码,正常;执行第一段代码,会抛出异常:

test_str.py:4: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal  my_str==my_unicode

这是因为,Python在做字符串对比时,会先自动将所有的字符串decode为unicode,然后再进行比较。在第一段代码中,由于Python的默认编码是ASCII,无法将中文字符串decode为unicode,所以抛出异常;在第二段代码中,Python的默认编码被修改为utf8,可以将中文字符串decode为unicode,所以执行正常。
  通过上述例子,可以发现,修改Python的默认编码,会导致代码执行结果的不一致,这并非我们想看到的,故而Python进行了删除setdefaultencoding()函数的操作,以防止用户对Python默认编码的修改。

4.unicode-escape、string-escape、raw-unicode-escape

  这一节我们介绍几种用来“转义”的编码方式:unicode-escape、string-escape、raw-unicode-escape。话不多说,直接看代码:

#unicode-escape>>> my_unicode=u"中文">>> my_unicode_escape="\u4e2d\u6587">>> my_unicodeu'\u4e2d\u6587'>>> my_unicode_escape'\\u4e2d\\u6587'>>> my_unicode.encode("unicode-escape")'\\u4e2d\\u6587'>>> my_unicode_escape.decode("unicode-escape")u'\u4e2d\u6587'
#string-escape>>> my_str="中文">>> my_str_escape="\\xe4\\xb8\\xad\\xe6\\x96\\x87">>> my_str'\xe4\xb8\xad\xe6\x96\x87'>>> my_str_escape'\\xe4\\xb8\\xad\\xe6\\x96\\x87'>>> my_str.encode("string-escape")'\\xe4\\xb8\\xad\\xe6\\x96\\x87'>>> my_str_escape.decode("string-escape")'\xe4\xb8\xad\xe6\x96\x87'
#raw-unicode-escape>>> my_str1=u"\xe4\xb8\xad\xe6\x96\x87">>> my_str2="中文">>> my_str1u'\xe4\xb8\xad\xe6\x96\x87'>>> my_str2'\xe4\xb8\xad\xe6\x96\x87'>>> my_str1.encode("raw-unicode-escape")'\xe4\xb8\xad\xe6\x96\x87'>>> my_str2.decode("raw-unicode-escape")u'\xe4\xb8\xad\xe6\x96\x87'

参考文献

[1] http://python.jobbole.com/82107/
[2] http://blog.csdn.net/trochiluses/article/details/16825269
[3] https://www.zhihu.com/question/31833164
[4] http://blog.csdn.net/xw_classmate/article/details/51933904
[5] https://www.python.org/dev/peps/pep-0263/
[6] http://blog.csdn.net/u012005313/article/details/48031281
[7] https://blog.ernest.me/post/python-setdefaultencoding-unicode-bytes
[8] http://blog.csdn.net/heybob/article/details/48373021
[9] http://python.usyiyi.cn/translate/python_278/reference/lexical_analysis.html
[10] http://kuanghy.github.io/2017/02/24/python-str-to-unicode-escape
[11] http://www.qmailer.net/archives/251.html
[12] http://blog.csdn.net/lrz4119/article/details/45247611
[13] https://mozillazg.github.io/2013/12/python-raw-unicode.html
[14] https://www.cnblogs.com/long2015/p/4090824.html
[15] http://python.jobbole.com/81244/
[16] https://www.2cto.com/kf/201411/355112.html
[17] http://www.cnblogs.com/harrychinese/archive/2012/01/19/change_python_default_encoding.html
[18] https://neo1218.github.io/reload/
[19] https://www.the5fire.com/why-need-reload-sys.html
[20] http://python-china.org/t/973
[21] http://www.jianshu.com/p/9686bfa7e81c
[22] http://blog.chinaunix.net/uid-29655675-id-4354608.html
[23] http://python-china.org/t/849
以上为本文的全部参考文献,对原作者表示感谢。

原创粉丝点击