美文网首页
python编码之痛

python编码之痛

作者: jshan | 来源:发表于2019-05-12 22:33 被阅读0次

    在python编程中,常常会出现一些编码上的问题,大致包括这三类问题: SyntaxError: Non-ASCII characterUnicodeDecodeErrorUnicodeEncodeError。下面我对日常遇到的编码问题做的一些小结,并解释上述三类问题出现的原因,并对日常编码做了一些规范化的建议,来避免上述这三类问题。

    python2.x 的编码解码介绍

    当对str进行编码(encode)时,会先用 默认编码 将str解码(decode)成unicode,然后再将unicode编码为你指定的编码。

    那么由于python2.x的默认编码(通过 sys.getdefaultencoding() 获取)是ascii,那么在处理中文的时候,因为ascii能够表示的字符范围有限,所以解码中文会有问题,常常报如下错误,例如

    string = "中国"
    print(s.encode('gbk'))  # 等价于 print(s.decode('ascii').encode('gbk'))
    

    UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

    如果要想正常输出,可以指定python的默认编码样式为 utf-8,当然也可以不修改python的默认编码,而是在string的编码前面先显性的执行解码命令,并且指定解码样式为 utf-8,例如

    import sys
    
    reload(sys)
    sys.setdefaultencoding('utf-8')
    
    string = "中国"
    print(s.encode('gbk'))  # 等价于 print(s.decode('utf-8').encode('gbk'))
    

    所以使用 unicode 命令创建 unicode 对象时,如果不说明这个字符串的编码格式,那么程序会使用python的默认编码,例如如下代码,如果没有设置默认编码的话,也会出现上面的 UnicodeDecodeError 错误

    string = unicode("中国")  # 等价于 string = unicode("中国", sys.getdefaultencoding())
    

    python2.x 的文件头声明编码

    我们在写python2.x文件时,一般会在文件头声明编码,例如如下代码

    # -*- coding: utf-8 -*-
    
    # 或者是如下代码,注意下面 = 符号两边没有空格
    
    # coding=utf-8
    

    做这个编码声明,是因为文件中有中文字符(不论是注释内容,还是字符串赋值)时,python2.x才知道如何去解析这些字符串,否则会出现如下错误

    SyntaxError: Non-ASCII character '\xe4' in file test.py on line 2, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

    另外做这个编码声明,python2.x会解码初始化 u"中国"unicode 对象

    有人会发现,我按照这样在文件头部声明了编码,怎么我在执行 print(u"中国") 的时候,会报如下错误呢?

    UnicodeEncodeError: 'latin-1' codec can't encode characters in position 0-1: ordinal not in range(256)

    出现这样的报错,是由于服务器的输出编码格式不能完成对 u"中国" 字符串的编码操作,通过shell命令 locale 查看到服务器的输出编码格式为 en_US (或者是通过python命令 sys.stdout.encoding 查看到为 ISO-8859-1,该编码和ascii编码相似,且Latin1是ISO-8859-1的别名),而 ascii 能够表示的字符范围有限,不能对所有的unicode完成编码操作。

    多重编码的字符串混合

    如果我们将多重编码格式的字符串进行混合使用时,下面将介绍这种情况。
    就像 int 和 float 类型的数据进行运算时,结果为 float,那么多重编码的字符串混合使用的时候,结果是什么格式的呢?

    python2在处理unicode字符串和str类字符串时,是将str类字符串转化为unicode字符串。例如如下例子

    string_1 = "Hello " + "中国"  # "Hello" 为ascii编码,"中国" 为utf-8编码,得到的结果为 utf-8编码
    
    string_2 = "Hello " + "中国" + u"美食" # u"美食"为unicode字符串,所以最终的结果应该是unicode编码,"Hello "容易decode为unicode,但是 "中国" 受制于python的默认编码(sys.getdefaultencoding())如果该值不是 utf-8 会报 UnicodeDecodeError 错误
    
    string_3 = "Hello " + "中国" + "美食".decode('utf-8').encode('gbk')  # ascii/utf-8/gbk 这三种不同编码的str类字符串相加,得到的结果是gbk编码的字符串
    

    关于编码的字节长度

    在计算字符串的字节长度的时候,使用命令 len,我们知道ascii编码的字符,一个该字符由1个字节组成;utf-8编码的字符,一个该字符由3个字节组成;gbk编码的字符,一个该字符由2个字节组成。也正是由于gbk编码的字符串所占用的字节少,所以一些网站就使用gbk编码。

    那我们对unicode的字符串使用 len 的话,得到的结果是字符数,不同于上面的字节数。

    具体可以查看下面的例子:

    # python2.x
    string = "中国"  # string编码为utf-8
    
    print(len(string))  # 6个字节 (= 2 * 3)
    print(len(string.decode('utf-8')))  # 2个字符 通过decode得到的是unicode字符串,通过len是在计算字符的数量
    print(len(string.decode('utf-8').encode('gbk')))  # 4个字节 (= 2 * 2) 通过decode和encode得到的是gbk编码的字符串
    
    string = u"中国"  # string编码为unicode
    
    print(len(string))  # 2个字符
    print(len(string.encode('utf-8')))  # 6个字节 (= 2 * 3)将string编码为utf-8字符串
    print(len(string.encode('gbk')))  # 4个字节 (= 2 * 2)  将string编码为gbk字符串
    

    关于requests库

    我们在爬虫或者是测试服务器响应数据的时候,常常使用requests库来操作,其中Request对象在访问服务器后返回一个Response对象,这个对象将返回的http响应字节码保存到content属性中,但是如果访问另外一个属性text时,会返回一个unicode对象,乱码就常常发生在这里,造成显示乱码。

    因为Response对象会通过另外一个属性encoding来将字节码编码成unicode,而这个encoding属性是reponse通过chardet猜出来的,例如如果爬取一个 gbk 网页的内容,就会出现乱码,可以通过如下方法解决:

    import requests
    
    url = "https://pvp.qq.com"
    response = requests.get(url)
    response.encoding = 'gbk'
    print(response.text)
    

    编码维护习惯

    基于以上的编码问题,这里列出一些我们在日常处理字符的一些规范,以此来统一我们的字符编码解码习惯,减少因为字符串编码的问题。

    1. 设置python的默认编码为 utf-8 (python2.x为 ascii)
    2. python代码文件的保存格式要与文件头部的声明编码保持一致,为 utf-8
    3. 如果是中文,程序内部尽量使用 unicode 而不是 str
    4. 保持程序内外的编码统一,即程序内只使用unicode,那么在从外部读取文件时,一定要将这些字节流转化为unicode,这样在后面的代码中只会去处理unicode,而不是str,例如如下的读取文件代码。
    # python2.x
    import codecs
    with codecs.open('text.py', 'r', encoding='utf-8') as f:
        for i in f:
            print(i)
    
    # python3.x
    with open('text.py', 'r', encoding='utf-8') as f:
        for i in f:
            print(i)
    

    python2.x 与 python3.x的编码区别

    字符串的类型(type),在python2.x和python3.x中有不同的表示,python2.x的编码分为str和unicode,而python3.x的编码分为bytes和str,且他们是相互对应的,即python2.x的 str 对应的是python3.x的 bytes;python2.x的 unicode 对应的是 python3.x的 str

    python2.x中所有的 ascii/utf-8/gbk 等编码的字符都是str类字符串,而python2.x默认的字符编码格式是ascii,str类字符串转换时,都需要通过unicode作为媒介。

    python3.x中所有的 ascii/utf-8/gbk 等编码的字符都是bytes类字符串,而python3.x默认的字符串编码格式是unicode,并且python3.x中str类字符串就是unicode类型的字符串。

    参考资料

    1. unicode之痛
    2. python2.x与python3.x编码区别

    相关文章

      网友评论

          本文标题:python编码之痛

          本文链接:https://www.haomeiwen.com/subject/qlnhaqtx.html