• 不得不知道的Python字符串编码相关的知识


    开发经常会遇到各种字符串编码的问题,例如报错SyntaxError: Non-ASCII character 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128),又例如显示乱码。
    由于之前不知道编码的原理,遇到这些情况,就只能不断的用各种编码decode和encode。。。。。
    今天整理一个python中的各种编码问题的原因和解决方法,以后遇到编码问题,就不会像莽头苍蝇一样,到处乱撞了。

    下面的python环境都是在2.7,听说在3.X中已经没有编码的问题了,因为所有的字符串都是unicode了,之后装个3.X试一下。

    如果不知道什么是decode和encode,建议先看一下:这里

    一、encoding的作用

    1.在python文件中,如果有中文,就一定要在文件的第一行标记使用的编码类型,例如 #encoding=utf-8 ,就是使用utf-8的编码,这个编码有什么作用呢?会改变什么呢?
    demo1.py

    # encoding=utf-8
    test='测试test'
    print type(test)
    print repr(test)
    

    输出:

    <type 'str'>
    'xe6xb5x8bxe8xafx95test'
    

    我们通过print把一个变量输出到终端的时候,IDE或者系统一般都会帮我们的输出作转换,例如中文字符会转成中文,所以就看不到变量的原始内容。
    repr函数可以看这个变量的给python看的形式,也就是看到这个变量的原始内容
    从上面的输出可以看到test变量的str类型,它的编码是utf-8的(怎么知道是utf-8,请看第三部分),也就是的encoding类型
    如果我们把encoding改为gbk
    demo2.py

    # encoding=gbk
    test='测试test'
    print type(test)
    print repr(test)
    

    输出

    <type 'str'>
    'xb2xe2xcaxd4test'
    

    这样test的编码类型就变为gbk了。
    所以这个encoding会决定在这个py文件中定义的字符串变量的编码方式。
    而如果一个变量是从其他py文件导入,或者从数据库,redis等读取出来的话,它的编码又是怎样的?
    a.py

    # encoding=utf-8
    test='测试test'
    

    b.py

    # encoding=gbk
    from a import test
    print repr(test)
    

    输出

    'xe6xb5x8bxe8xafx95test'
    

    a.py中定义test变量,a.py的编码方式是utf-8,b.py的编码方式是gbk,b从a中导入test,结果显示test依然为utf-8编码,也就是a.py的编码
    所以encoding只会决定本py文件的编码方式,不会影响导入的或者从其他地方读取的变量的编码方式

    二、常见报错codec can't encode characters的原因

    python的程序经常会报错 codec can't encode characterscodec can't decode characters

    在python中定义一个字符串,

    import sys
    print sys.getdefaultencoding() # 输出 ascii
    unicode_test=u'测试test'
    print repr(str(unicode_test))
    

    上面的代码会报错

     'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
    

    除了str方法外,如果操作两个都有中文的字符串,也会报错,但是只有其中一个有中文,却不会报错

    unicode_test = u'测试test%s{0}'
    
    print '%stest' % unicode_test  # 不会报错
    print '%s测试' % unicode_test  #会报错
    
    print unicode_test % 'test'  #不会报错
    print unicode_test % '测试'  #会报错
    
    print unicode_test.format('test') #不会报错
    print unicode_test.format('测试') #会报错
    
    print unicode_test.split('test')  #不会报错
    print unicode_test.split('测试')  #报错
    
    print unicode_test + 'test'  #不会报错
    print unicode_test + '测试'  #会报错
    

    为什么会这样?
    这原因下面再解答,这里先列出这个报错的解决方法:
    解决方法是:把系统的默认编码设置为utf-8

    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    print sys.getdefaultencoding()
    unicode_test=u'测试test'
    

    demo3.py
    # encoding=utf-8
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    unicode_test=u'测试test'
    utf8_test='测试test'
    gbk_test=unicode_test.encode('gbk')

    #合并unicode和utf-8
    merge=unicode_test+utf8_test
    print type(merge)
    print repr(merge)
    
    #合并unicode和gbk
    merge=unicode_test+gbk_test
    print type(merge)
    print repr(merge)
    print merge
    
    #合并utf-8和gbk
    merge=utf8_test+gbk_test
    print type(merge)
    print repr(merge)
    print merge
    

    这里定义三个分别是unicode,utf-8和gbk编码的字符串,unicode_test,utf8_test和gbk_test
    1.合并unicode和utf-8的时候,输出:

    <type 'unicode'>
    u'u6d4bu8bd5testu6d4bu8bd5test'
    

    合并的结果的编码是unicode编码。
    2.合并unicode和gbk,会报错:

    'utf8' codec can't decode byte 0xb2 in position 0: invalid start byte
    

    所以我们可以推测:
    在python对两个字符串进行操作的时候,如果这两个字符串有一个是unicode编码,有一个是非unicode编码,python会将非unicode编码的字符串decode成unicode编码,再进行字符串操作
    例如合并字符串的操作可以写成以下的function:

    def merge_str(str1, str2):
        if isinstance(str1, unicode) and not isinstance(str2, unicode):
            str2 = str2.decode(sys.getdefaultencoding())
        elif not isinstance(str1, unicode) and isinstance(str2, unicode):
            str1 = str1.decode(sys.getdefaultencoding())
        return str1 + str2
    

    PS:sys.getdefaultencoding()的初始值是ascii
    所以,
    codec can't encode(decode) characters 这个报错是encode或decode这两个方法产生的,而这个方法的参数是sys.getdefaultencoding()。如果用ascii编码对带有中文的字符串进行解码,就会报错。所以修改系统的默认编码可以避免这个报错。
    当执行 str 操作时,python会执行 unicode_test.encode(sys.getdefaultencoding()) ,所以也会报错。

    3.#合并utf-8和gbk的时候却不会报错,python会直接把两个字符串合并,不会有decode或encode的操作,但是输出的时候,部分字符串会乱码。
    demo4.py

    # encoding=gbk
    import sys
    
    reload(sys)
    sys.setdefaultencoding('utf-8')
    unicode_test = u'测试test'
    utf8_test = unicode_test.encode('utf-8')
    gbk_test = unicode_test.encode('gbk')
    
    merge = utf8_test + gbk_test
    print type(merge)
    print repr(merge)
    print merge
    

    这里文件的encoding是gbk,sys.getdefaultencoding()设置为utf-8,结果是:

    <type 'str'>
    'xe6xb5x8bxe8xafx95testxb2xe2xcaxd4test'
    测试test����test
    

    即gbk的部分乱码了。所以输出的时候会按照sys.getdefaultencoding()的编码来解码。

    三、怎么判断一个字符串(string)的编码方式

    1. 没有办法准确地判断一个字符串的编码方式,例如gbk的“aa”代表甲,utf-8的“aa”代表乙,如果给定“aa”怎么判断是哪种编码?它既可以是gbk也可以是utf-8

    2. 我们能做的是粗略地判断一个字符串的编码方式,因为上面的例如的情况是很少的,更多的情况是gbk中的'aa'代表甲,utf-8中是乱码,例如�,这样我们就能判断'aa'是gbk编码,因为如果用utf-8编码去解码的结果是没有意义的

    3. 而我们经常遇到的编码其实主要的就只有三种:utf-8,gbk,unicode

      • unicode一般是 u 带头的,然后后面跟四位数字或字符串,例如 u6d4bu8bd5 ,一个 u 对应一个汉字
      • utf-8一般是 x 带头的,后面跟两位字母或数字,例如 xe6xb5x8bxe8xafx95xe5x95x8a ,三个 x 代表一个汉字
      • gbk一般是 x 带头的,后面跟两位字母或数字,例如 xb2xe2xcaxd4xb0xa1 ,两个个 x 代表一个汉字
    4. 使用chardet模块来判断

    import chardet
    
    raw = u'我是一只小小鸟'
    print chardet.detect(raw.encode('utf-8'))
    print chardet.detect(raw.encode('gbk'))
    

    输出:

    {'confidence': 0.99, 'encoding': 'utf-8'}
    {'confidence': 0.99, 'encoding': 'GB2312'}
    

    chardet模块可以计算这个字符串是某个编码的概率,基本对于99%的应用场景,这个模块都够用了。

    四、string_escape和unicode_escape

    1. string_escape

    在str中,x是保留字符,表示后面的两位字符表示一个字符单元(暂且这么叫,不知道对不对),例如'xe6',一般三个字符单元表示一个中文字符
    所以在定义变量时,a='xe6x88x91',是代表定义了一个中文字符“我”,但是有时候,我们不希望a这个变量代表中文字符,而是代表3*4=12个英文字符,可以使用encode('string_escape')来转换:

    'xe6x88x91'.encode('string_escape')='\xe6\x88\x91'
    

    decode就是反过来。
    转换前后的类型都是string。
    还有一个现象,定义a='x',a='x0'都是会报错ValueError: invalid x escape的,而定义a='a',即反斜杠后面不是跟x,都会没问题,而定义a='x00',即x后面跟两个字符,也是没问题的。

    2. unicode_escape

    同理在unicode中,u是保留字符,表示后面的四个字符表示一个中文字符,例如b=u'u6211',表示“我:”,同理我们希望b变量,表示6个英文字符,而不是一个中文字符,就可以使用encode('unicode-escape')来转换:

    u'u6211'.encode('unicode-escape')='u6211'
    

    注意encode前是unicode,转换后是string。
    在unicode中,u是保留字符,但是在string中,就不是了,所以只有一个反斜杠,而不是两个。
    decode就是反过来。
    同理,a='u'也是会报错的

    3. 例子

    #正常的str和unicode字符
    str_char='我'
    uni_char=u'我'
    print repr(str_char) # 'xe6x88x91'
    print repr(uni_char) #  u'u6211'
    
    # decode('unicode-escape')
    s1='u6211'
    s2=s1.decode('unicode-escape')
    print repr(s1) # '\u6211'
    print repr(s2) # u'u6211'
    
    # encode('unicode-escape')
    s1=u'u6211'
    s2=s1.encode('unicode-escape')
    print repr(s1) # u'u6211'
    print repr(s2) # '\u6211'
    
    # decode("string_escape")
    s1='\xe6\x88\x91'
    s2=s1.decode('string_escape')
    print repr(s1) # '\xe6\x88\x91'
    print repr(s2) # 'xe6x88x91'
    
    # encode("string_escape")
    s1='xe6x88x91'
    s2=s1.encode('string_escape')
    print repr(s1) # 'xe6x88x91'
    print repr(s2) # '\xe6\x88\x91'
    

    4. 应用

    1. 内容是unicode,但是type是str,就可以使用decode("unicode_escape")转换为内容和type都是unicode

       s1='u6211'
       s2=s1.decode('unicode-escape')
      
    2. 内容是str,但是type是unicode,就可以使用encode("unicode_escape").decode("string_escape")转换为内容和type都是str

       s1=u'xe6x88x91'
       s2=s1.encode('unicode_escape').decode("string_escape")
      

    博文为作者原创,未经允许,禁止转载。

  • 相关阅读:
    ELK扫盲及搭建
    重构支付接口(二)重构方案
    重构支付接口(一)支付接口存在的问题
    redis的持久化(RDB与AOF)
    死锁、活锁、性能问题
    Android控件_RecycleView+CarView+Palette联合应用
    窗体间传值 ,子窗体传给父窗体
    【Android-自定义控件】 漂亮的Toast
    【Android-关闭所有Activity】关闭activity之前的所有activity,重启actibity
    【Android-自定义控件】SwipeRefreshDemo 下拉刷新,上拉加载
  • 原文地址:https://www.cnblogs.com/Xjng/p/5093905.html
Copyright © 2020-2023  润新知