起因
今天让实习生用python写一个简单的流程,第一次问的时候说写好了执行没问题现在加到定时任务里面,隔了30分钟又问了一次说有点问题,手动执行结果正确,但是写到定时任务里面就有问题。What?
仔细问了一下,原来是程序原本没有问题,但是如果把文件输出重定向到文件之后执行结果就不正确了。我说:没有任何报错?他摇头说没有。抱着怀疑的态度我打开了他的代码。。
果然!全是try/except,更加无语的是except下面全都是跟的continue,我心想,这能报错才怪嘞。然后就是他说的另外一个问题为什么重定向到文件就会报错嘞,联想到他这个try/except,我猜应该是在循环内部有一个print语句,由于输出时候的编码问题导致报错,但是全都被try捕获到并且静悄悄的continue了,仔细一看果然有一行print。
其实这个问题的根源就在于python2版本中直接print字符串的情况下打印到终端没有问题,但是在含有中文一类的字符时如果是python code.py > debug这样来写是会出现编码问题的。
解决办法有两种,下面解释完原因会详细讲一下。
原因梳理
复现
这里首先对问题进行一下复现,考虑如下代码
1 | #!/bin/env python |
以上代码执行结果其实很简单,在终端中结果为这是中文,但是如果重定向到文件,那么文件中会有两行输出分别是
这是中文
‘ascii’ codec can’t encode characters in position 0-3:ordinal not in range(128) 。
错误复现了。
前置知识储备
其实错误的根源在于python2内部对于字符串的处理,默认python2中字符串使用的是str字符串类型(每个字符占用一个字节),而当我们在其前面写上u的时候表明这个字符串是一个unicode字符串,其与str类型就不再一样了变成了一个unicode类型。
将字符串写入文件其本质上是将二进制信息写入文本,所以这里理解一下就是我们最终写到文件里面的是二进制信息。所以最终到文件之前其实最终是要转化为byte类型的。
这里Stack Overflow上有个问题上面有很好的介绍
Encoding: characters → bytes
Decoding: bytes → characters
以上我们可以看出来通过调用Encoding以及Decoding对字符进行编码和解码。
问题原因
其实说了这些,问题的原因其实就在于python2在将字符串写入的时候发生了错误,ascii无法对字符串进行encode导致报错,看到这里意识到什么没有,报错的原因是ascii无法encode,这个是必然的,因为ascii只能表示0~128这么一个范围的字符,而汉字明显是不在这个区间的,所以本质上是由于python2在进行转化的时候使用了错误的编码导致。
那么为什么直接写入终端没有问题,而写入文件就会报错嘞?
这是由于在写入终端的情况下python2能获取到要写入的介质的编码类型,而重定向文件这种情况下是获取失败的,仍然是上代码
1 | #!/bin/env python |
1 | first |
可以看到,写入终端的情况下编码为utf8而写入文件的时候变成了None,这种情况下python2会调用其内置默认的ascii进行encode于是就发生了开头所说的错误
问题的解决
问题的原因也知道了,那么解决方法也就很明了了,就是让字符串正确的decode就ok了,所以有如下几种方法:
- 在代码的开始调用reload(sys);sys.setdefaultencoding(‘utf8’)通过这种方式,我们制定了默认的encode字符集为utf8因此修正了以上错误
- 在print u1的地方改成print u1.decode(‘utf8’).encode(‘utf8’)由我们来指定调用的字符集防止其调用默认的ascii link
再深入一些
Python的字符串深入了解一下,考虑如下代码:
1 | #!/bin/env python |
1 | 这是中文 |
这个例子从二进制的角度来对python2的字符串进行输出,例如对于unicode类型的字符串来说每个汉字表示一个元素,而一个汉字占用了三个字节,而对于python2内置的字符串类型,每一次遍历都是一个字节一个字节遍历的。同时需要注意的是并不是所有的unicode在使用默认ascii进行encode的时候都会报错,例如u1字符串在输出的时候就不会报错,因为1并没有超出ascii的编码范围 :)。
我觉得说到这里,python代码开头的coding这一行应该也不需要过多说明了吧?
参考资料
Stack Overflow:https://stackoverflow.com/questions/4545661/unicodedecodeerror-when-redirecting-to-file
字符集的介绍:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
Python官方文档:https://docs.python.org/2/howto/unicode.html
关于python3
细心的同学一定发现了我上面说的全都是python2特意指明了是2才会这样,因为python 3是没有以上这些问题的,考虑到python混乱的字符问题,python 3默认使用的不再是str而是unicode,考虑如下代码,python3当中字符串已经不简单是str了,而是变成了class ‘str’ :)
1 | #!/bin/env python |
1 | str |
好啦,就到这里啦:)
转载请注明来源链接 http://just4fun.im/2017/11/30/python编码问题/ 尊重知识,谢谢:)