稀有猿诉

十年磨一剑,历炼出锋芒,说话千百句,不如码二行。

Python字符串编码答疑

Python 2中的字符串分类

在Python 2中字符串,有两个类型,一个是str,一个是unicode。str可以理解为ASCII的字符列表,说白了,只能存储ASCII字符,如果赋个中文值,会报错;而unicode是支持非ASCII字符的。这个与C语言中的字符非常类似char[]只能存ASCII字符串,而wchar[]才可以存储Unidcode字符串。

可以用如下方式来安全的转换两种字符串:

1
2
3
4
5
6
7
8
9
10
11
def to_str(foo):
   if foo instanceof unicode:
      return str(foo)
   else:
      return foo

def to_unicode(bar):
   if bar instanceof str:
      return unicode(bar)
   else:
      return bar

对于字面字符串,前面加上u来标识是一个unicode,否则会当成str:

1
2
a = 'this is a string'
b = u'nah, I am a unicode'

Python 3中的字符串分类

由于Python 2中的混乱,所以到了Python 3里面,有了新的定义,字符串类型是str,支持Unicode,另外多了一个类型bytes,可以理解为byte的列表,也即是0<=且<=256的无符号整数,或者说是ASCII字符,简单来理解意思是一样的。这个就与Java语言类似了,如同Java中一样,String支持unicode,另专门有byte类型。另外,对于字面字符串也无需要再要标识了。

str与bytes之间的关系与区别是,str是给人看的,而bytes是给机器看的,bytes是str的底层实现。所以,bytes处理起来更快,效率更高,一些底层的IO库以及像网络IO,用的数据 一般都是bytes。它们之间是可以互转的:

  • str转为bytes叫encode
  • bytes转为str叫decode

在转换的时候还需要指定编码格式,这个编码格式就是unicode的编码方式,默认是’utf-8’,这里就涉及Unicode编码解码的相关知识了,常见的就是’utf-8’,’unicode’,以及’gbk’等。可以用如下方法来安全的转换:

1
2
3
4
5
6
7
8
9
10
def to_bytes(foo):
   if foo instanceof str:
      return foo.encode()
   else:
      return foo
def to_str(bar):
   if bar intanceof bytes:
      return bar.decode()
   else:
      return bar

注意区分两个编码格式

需要注意区分两个编码格式的设置,一个是指定程序里面字符串的编码,如在encode()和decode()时指定编码格式。

另外,一个就是程序源文件的编码格式,这个容易被忽略,要详细说下:程序的源码,其实就是一个文本文件,对吧,那么这个文本文件也是要指定编码格式的,常规来说,Python程序应该都是ASCII字符,所以默认的呢Python解释器,是按照ASCII文本的方式来处理代码源文件,但我们代码里面是会出现Unicode字符的,比如字面字符串,或者写的注释,如果 不进行特殊的设置解释器会报错的,因为出现了它不认识的字符。这时就需要对源码文件设置一下编码格式,把这句加在代码源文件的最上面,就好了:

1
# -*- coding: utf-8 -*-

如何解决未知编码格式

关于编码最容易遇到的就是UnicodeDecodeError,后面跟着一坨详细信息,这个错误就是告诉你编码时出问题了,通常有两类错误:

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

    这个通常是在Python 2时会遇到,原因简单的来说就是把unicode当成了string,或者源码文件的编码格式不对。解决的办法就是按照 上面介绍的安全转换方法,另外要设置一下源码文件的编码格式,这个问题自然可解。

  • Python3.6 UnicodeDecodeError: ‘utf8’ codec can’t decode byte 0xb2 in position 24137: invalid start byte

    这个通常是在Python 3时面遇到的,后面的具体信息会不一样,这个问题会发生在bytes转化为string,decode时会报出,原因就是指定的解码方式与真实的不匹配,无法成功decode,比如,bytes是「gtk」的,但是用’utf-8’去decode肯定会失败。这个问题,在写爬虫或者一些文本处理时经常遇到,因为来源的编码方式不固定。

    这个问题,如果想要根解,必须预知来源bytes的编码格式,但这通常不可能,所以可行的一条方案是,用一坨编码方式来不停的尝试:

    def safe_decode(source):
       encodeings = ['utf-8', 'gbk', 'utf-16']
       for en in encodeings:
          try:
             return source.decode(en)
          except UnicodeDecodeError as e:
             print('Failed: ', e)
       return source
    

Comments