Py学习  »  Python

字符编码(四)|python2中的编码问题

dwzb • 6 年前 • 429 次点击  

这是编码系列最后一篇,只讲有关python2的部分。

python2与python3相比有些设定引起了很多莫名其妙的编码问题,我们通常诟病python2在编码方面的坑其实指的就是这部分莫名其妙的问题。

我们通常说的正常的编码问题是上文提到的python3中会遇到的编码问题

  • 文件或网页的编码方式和读取时使用的方式不同,这是非常正常的,解决思路也非常清晰,只要使用正确的编码就可以了
  • encodedecode方法编码和解码时,使用错误编码造成的错误,这也是找到正确的编码就可以解决

而说python2中的很多编码问题莫名其妙,是因为这些编码解码不是人为指定的,而是经常在你不知道的时候,程序就按照某种编码方式对某些字符进行了编码或解码,而它使用的这种编码方式还不正确,导致了报错或乱码。此时使用者会觉得自己根本没有做编码或解码的工作怎么就会发生编码方面的错误。下面就来大致讲一讲这些问题出现的原因,相信看完上面的基础,理解python2中的问题是非常容易的。

这些莫名其妙的编码问题主要是由两个默认编码造成的

  • py2中默认编码方式是ASCII,py3是UTF-8(这个默认编码在什么时候会遇到后面会讲),这个是引起编码问题的主要原因
  • py2中用a='中文'定义的字符串其实不是真正的字符串,它不是Unicode,所以说在定义a时默认进行了一次编码转换,这个编码转换和上面的ASCII还没有关系,使用的是另一种默认的编码

上那两个默认编码在python3中几乎不会用到,它们产生的原因在于pyhton2设计上的缺陷,下面就列一下python2是如何导致的编码错误,一共5种错误。

1.定义str时的自动编码

在python2中,a='中文'这个a不是Unicode,b=u'中文'这个b才是。而在python3中根本没有u'中文'这一说,a='中文'这个a就是Unicode。

准确地说,a叫字节串,b才叫字符串(前面提到过Unicode和字符串完全等同)。a其实是一个二进制字节流,是字符串encode后的产物。所以说在a='中文'这个赋值过程中,默认进行了一次编码encode,将’中文’这个字符串编码成了二进制数。

既然是编码,肯定是按照某一套编码方式来做的。这个编码方式再不同场景是不一样的。用下面代码查看当前默认编码是什么

import locale
locale.getdefaultlocale()

这就是两种默认编码中的第二种。

一般在windows命令行下结果是('zh_CN', 'cp936'),即使设置了chcp 65001,或者在jupyter中使用也是一样是cp936。

说明定义字符串(准确来说应该叫字节串)时,在windows下都会默认用GBK进行编码。

字符串的两种定义方式会让使用者产生混乱,更容易触犯到后面的那些雷区。

解码时如果不能理解它原来是怎么编码的就会使用错误的编码从而造成报错。

2.str和unicode混用造成的错误

由于第一条中的差异,a='中文'这样定义的a在编码和解码方面也与py3是有不同的。

  • 在python3中,这样定义的a只能encode,因为它是Unicode
  • 在python2中,这个a主要是要decode,因为它是编码过的

从这一点中,第一种默认编码上场了。

如果str和unicode混用,比如'中文'+u'好',这个过程会默认将str解码成unicode,使用的是py2默认的ASCII编码,而中文不对应ASCII编码,于是报错。这是第一种默认编码产生错误的第一种,之后还有很多地方都会有类似的默认转换。

在这里首先说一下如何查看这个默认编码是什么

import sys
sys.getdefaultencoding()

就是这个命令,在py2中结果是ascii,在py3中就是utf-8。涉及到的两个默认编码都出现了,查看命令也不一样,注意区分。

另外再说一下关于这点python3是怎么设计的。如果str和bytes直接相加,会直接报出一个错误TypeError: Can't convert 'bytes' object to str implicitly,python3说的很明确不能隐式转,而不是像python2一样偷偷给你转了。

3.py2编码解码通用不规范造成的错误

这里要列两点py2和py3的差别

  • py3中的两个type:str和bytes分别对应py2中的unicode和str。python2中,a='中文'这个a就是str类,b=u'中文'这个b就是unicode类
  • 在py2中,无论unicode类还是str类,都可以使用encode和decode方法,而py3中明确str只能使用encode,bytes只能使用decode

在py2中,按理说str应该只能decode,而unicode应该只能encode,而现在开放了全都可以,让使用者更搞不清楚到底应该decode还是encode,如果用错了会出现如下错误

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

其中第一条是将unicode强行decode的报错,第二条是将str强行encode的报错。对于这两种强行解码编码的内部运行机制是

  • unicode强行decode时,因为unicode确实不能decode,于是py2会默认将unicode用ascii encode一下,再用得到的结果去decode,就在encode的过程中就报错了
  • str强行decode时,因为str确实不能encode,于是py2会默认将str用ascii decode一下,再用得到的结果去encode,就在decode的过程中就报错了

这是第一种默认编码产生错误的第二种情况

4.print时的默认编码

我们之前提到过,a='中文'这样定义的a其实本身是字节串而不是字符串,在控制台中输入a的输出结果和输入print(a)的输出结果是不一样的

  • 输入a的输出结果是16进制形式的,也就是它的本来面貌
  • 输入print(a)的输出结果是我们能看懂的字符’中文’

说明在print时其实进行了一次默认解码,将字节转换成了unicode呈现出来。这时print的是str类型,如果传入的是unicode类型,又会自动按照ascii编码将unicode encode为str,然后再print出来,如下所示

b = u'中文'
print(b)
# UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

这时第一种默认编码产生错误的第三种情况。

5.函数参数类型传入错误

函数参数本应传入str类型,而传入的是unicode类型的时候,也会自动使用ascii编码encode

这个其实可以算是第4条print的推广,strraw_input函数都是这样

b = u'中文'
print(b)
str(b)
a = raw_input(b)

后三行分别指定都会报错,也是encode用ascii编码的问题,这时第一种默认编码产生错误的更多情况。

python2中的编码问题参考三篇非常好的文章

因为我的电脑里没有装python2,一切测试结果都在这个网站中进行

专栏信息

专栏主页:python编程

专栏目录:目录

版本说明:软件及包版本说明


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/oFr8bxjyCK
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/8736
 
429 次点击