2016年11月10日星期四

Python2中奇葩的编码问题整理



写这篇文章的目的很简单,之前写HTTP批量获取网站Title的时候,就遇到或这种奇葩问题:
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd6 in position 3: ordinal not in range(128)

此外,把代码从osx搬到win上执行的时候,也遇到这种问题。
今天在处理eml文件的时候,同样的代码在osx上执行没问题,在win上执行的时候又报这种让人一脸懵b的问题。


OS之间编码环境的差异


#获得Python的默认编码ascii
import sys
sys.getdefaultencoding()

#获得当前系统中文件系统使用的默认编码
import sys
sys.getfilesystemencoding()

以下是若干个环境测试结果
windows xp(默认语言环境GBK)













windows 2003 (默认语言环境US)



windows 2008 (默认语言环境GBK)
















OSX10.9(默认语言环境UTF-8)



Windows下比较奇葩,全都返回mbcs,既多字节字符编码。直接拿这个去decode,Python解释器可不乐意。我们需要得到更精准的描述(cmd中执行chcp可得到当前系统所使用的编码,在控制面板的区域中可以的得到,只不过里面给的并不是特定的编码,而是地区/国家)。

如何通过py代码获得系统默认编码呢?

import locale
import codecs
print locale.getpreferredencoding();
print codecs.lookup(locale.getpreferredencoding()).name
这段代码在Windows 中文系统中返回gbk。
在Linux,OSX中返回utf-8。

到此编码环境确定了,Windows下的编码会经常变化,而Linux系统则不会。


Python程序对编码的处理


Python的代码,强烈建议将所有输入都转为unicode进行处理(根据输入的来源,进行decode处理)在根据需求,将unicode转换为特定的编码进行保存。不然你的python程序同时在linux,win上执行时,可能会出现很奇葩的行为。

将输入来源进行分类:

  • 当前系统:locale获取当前系统默认编码
  • 外部系统:根据来源系统,进行特定的编码处理


    1. 其他操作系统:Linux默认为UTF-8,windows由local获得或cmd中执行chcp
    2. http:Content-Type头,还有html页面中的meta标签都可能包含编码信息。
    3. 数据库:


      • Mysql:
      • Oracle:
      • MSSQL:

赠送一个unicode查询网站,在赠送一个ppt,也是介绍编码的。


还有一个有编码信息的地方,py文件的头部
#coding=gbk
这段代码定义了当前py文件中的字符串的编码。
既当前文本中的字符串在转换为unicode,只能通过decode,coding所指定的编码获得。


建议设置一个全局变量,用来控制当前os中用户的输入。

奇葩问题集

输出


  • 输出到终端:Unicode类型可以直接输出到终端
  • 持久化:(需要进行特定编码后才可保存)
    • 数据库
    • 文本:
      • ascii编码:open打开的文本,只能进行ascii读写
      • 非ascii编码:codecs.open,指定encoding类型,进行读写


来自不同终端的输入

来自终端的输入,都是带有特定编码的。比如中文的win下,默认编码是gbk。
OSX下,默认是utf-8。为了避免出现UnicodeDecodeError的问题。需要根据当前OS的默认编码对输入进行decode处理。raw_input得到的输入,需要decode之后再用。但是不同安装语言的win下,默认编码不同。因此,raw_input().decode的参数需要通过locale的getprederredencoding获得。

import locale
default_os_encoding = locale.getpreferredencoding();
raw_input().decode(default_os_encoding)

os.walk的问题


这玩意儿根据传入的参数类型(str,unicode)不同,返回的dirpath,dirnames,files类型也不同(传str,返回str。传unicode,返回unicode)。官方文档里也没这描述,只有os.listdir有对此行为进行描述。

os.walk以在osx系统与windows中的行为存在差异:
osx上,以str类型作为输入,在pycharm中的调试结果






windows上,以str类型作为输入,在pycharm中的调试结果(这些都是什么鬼)

Windows 2008 R2,E文














Windows 2008 R2,毛子文





Windows 2008 R2,中文




以unicode类型作为os.walk的输入,得到一组unicode类型的返回,在win下与osx在结构都正常。
因此,os.walk比较稳妥的用法:
for dirpath, dirnames, files in os.walk(raw_input.decode(locale.getpreferredencoding()))