当前位置: 首页 > 科技观察

为什么Python编码如此痛苦?

时间:2023-03-19 18:39:11 科技观察

据说每一个做Python开发的人都被字符编码的问题搞糊涂了。最常见的错误是UnicodeEncodeError和UnicodeDecodeError。你似乎知道如何解决它们。不幸的是,错误出现在其他地方。问题总是重蹈覆辙,str和unicode之间的转换使用decode或者encode的方式不好记,总是一头雾水,问题出在哪里?为了弄清楚这个问题,我决定从python字符串的组成和字符编码的细节来分析字节和字符计算机中存储的所有数据。文字字符、图片、视频、音频、软件都是由一串01字节组成的序列,一个字节等于8位。字符是一个符号。例如汉字、英文字母、数字、标点符号都可以称为字符。字节用于存储和网络传输,而字符用于显示和阅读。例如,硬盘上存储的字符“p”是一串二进制数据01110000,占用一个字节来对我们用编辑器打开的文本进行编解码。我们看到的字符最后保存到磁盘上时都是二进制字符。以节序列的形式存储。那么字符到字节的转换过程称为编码(encode),编码又称为解码(decode),两者是一个可逆的过程。编码是为了存储和传输,解码是为了显示和读取。例如,字符“p”被编码为二进制字节序列01110000的字符串保存到硬盘中,占用一个字节的长度。字符“Zen”可以存储在“111001111010011010000101”的长度中,占用3个字节。为什么可能?这个以后再说吧。为什么Python编码如此痛苦?当然,这不能怪开发商。这是因为Python2使用ASCII字符编码作为默认的编码方式,而ASCII无法处理中文,为什么不使用UTf-8呢?因为Guido神父在1989年冬天为Python写了第一行代码,所以第一个版本在1991年2月正式开源发布,1991年10月Unicode发布,也就是说Python这个语言被创造出来了这是其中之一UTF-8还没有诞生的时代。Python还把字符串分为unicode和str两种类型,让开发者一头雾水,这是第二种。python3对字符串进行了彻底改造,只保留一种类型。这是有话要说的,以后再说。str和unicodePython2将字符串分为unicode和str两种类型。本质上,str是一串二进制字节序列。从下面的示例代码可以看出,str类型的“Zen”十六进制打印为\xec\xf8,对应的二进制字节序列为'1110110011111000'。>>>s='Zen'>>>s'\xec\xf8'>>>type(s)和unicode类型u"Zen"对应于unicode符号u'\u7985'>>>u=u"Zen">>>uu'\u7985'>>>type(u)如果我们要将unicode符号保存到文件或传输到网络,我们需要将它们转换编码后变成str类型,所以python提供了encode方法实现unicode转str,反之亦然。encode>>>u=u"Zen">>>uu'\u7985'>>>u.encode("utf-8")'\xe7\xa6\x85'decode>>>s="Zen">>>s.decode("utf-8")u'\u7985'>>>很多初学者记不住str和unicode的转换是用encode还是decode。如果你还记得str本质上是一串二进制数据,而unicode是一个字符(符号),而编码(encode)是将一个字符(符号)转换成二进制数据的过程,所以从unicode到str的转换需要编码方法,反之亦然。encoding总是取一个Unicode字符串,返回一个bytes序列,decoding总是取一个bytes序列,返回一个Unicode字符串。理清str和unicode的转换关系后,我们看看什么时候会出现UnicodeEncodeError和UnicodeDecodeError。UnicodeEncodeErrorUnicodeEncodeError发生在将unicode字符串转换为str字节序列时。让我们看一个例子,将一串unicode字符串保存到一个文件#-*-coding:utf-8-*-defmain():name=u'ZenofPython'f=open("output.txt","w")f.write(name)错误日志UnicodeEncodeError:'ascii'codeccan'tencodecharactersinposition6-7:ordinalnotinrange(128)为什么会出现UnicodeEncodeError?因为在调用write方法的时候,Python会先判断字符串的类型。如果是str,会不经过编码直接写入文件,因为str类型字符串本身就是二进制字节序列的字符串。如果字符串是unicode类型,会先调用encode方法将unicode字符串转成二进制str类型再保存到文件中,encode方法会使用python默认的ascii码进行编码:>>>u"Python之禅".encode("ascii")但是,我们知道ASCII字符集只包含128个拉丁字母,不包括汉字,所以会出现'ascii'codeccan'tencodecharacters错误。要正确使用encode,必须指定包含汉字的字符集,如:UTF-8、GBK。>>>u"Python禅".encode("utf-8")'Python\xe4\xb9\x8b\xe7\xa6\x85'>>>u"Python禅".encode("gbk")'Python\xd6\xae\xec\xf8'所以要想把unicode字符串正确写入文件,应该提前把字符串转成UTF-8或者GBK。defmain():name=u'ZenofPython'name=name.encode('utf-8')withopen("output.txt","w")asf:f.write(name)当然,把unicode字符串正确写入文件的方法不止一种,但原理都是一样的,这里不再介绍。将字符串写入数据库和传输到网络是同一个原理。UnicodeDecodeErrorUnicodeDecodeError将str类型的字节序列解码为Forunicode类型的字符串>>>a=u"Zen">>>au'\u7985'>>>b=a.encode("utf-8")>>>b'\xe7\xa6\x85'>>>b.decode("gbk")Traceback(最近调用最后):文件“”,第1行,在UnicodeDecodeError:'gbk'编解码器can'tdecodebyte0x85inposition2:incomplete当多字节序列将UTF-8编码生成的字节序列'\xe7\xa6\x85'转换为GBK解码的unicode字符串时,出现UnicodeDecodeError,因为(对于汉字)GBKencoding只占两个字符段,而UTF-8占3个字节,用GBK转换多一个字节,无法解析。避免UnicodeDecodeError的关键是编码和解码时使用相同的编码类型。这也回答了文章开头提到的“禅”字。保存在文件中可能占用3个字节,也可能占用2个字节,具体取决于编码时指定的编码格式。UnicodeDecodeError的另一个示例>>>x=u"Python">>>y="Zen">>>x+yTraceback(最近一次调用):文件“”,第1行,在UnicodeDecodeError中:'ascii'codeccan'tdecodebyte0xe4inposition0:ordinalnotinrange(128)>>>str和unicodestring执行+操作,Python会隐式地将str类型的字节序列转换成(decode)成相同的unicode类型x,但是Python使用默认的ascii编码进行转换,而ASCII不包含中文,所以报错。>>>y.decode('ascii')Traceback(最近调用最后):文件“”,第1行,在UnicodeDecodeError:'ascii'编解码器无法解码位置0中的字节0xe4:序号notinrange(128)的正确方法应该是明确地使用UTF-8或GBK解码y。>>>x=u"Python">>>y="Zen">>>y=y.decode("utf-8")>>>x+yu'Python\u4e4b\u7985'以上都是基于关于Python2,会另外写一篇关于Python3的字符和编码的文章,敬请期待。