原作者:wuyou @newsmth
Python2 的编码问题一直非常让人恼火,不过你的问题稍微简单一些。
在交互式命令模式(Interactive shell)下,可以这样来明白你遇到的问题的根源:>>> import sys
>>> sys.stdout.encoding
在 Windows 下,如果你的系统是简体中文,一般输出会是 cp936
>>> import codecs
>>> codecs.lookup('cp936').name
'gbk'
这就是在 Windows 终端里面使用的编码。在 print 的时候,无论字符串源采取什么样的编码,最终输出的字符串的编码必须跟 shell 的编码一致,也就是说:
>>> print some_string
Python 会做这样一个动作:
codecs.encode(some_string, coding, errors='strict')
在你遇到的情况下,some_string 是一个 unicode 字符串,coding 是 gbk。由于你的 unicode 字符串里面包含 gbk 字符集里面没有的字符,Python 就会抛出一个 UnicodeEncodeError.
对于输出到文件中情况是类似的,Python2 的 open() 函数不会传入 encoding,以 'w' 方式打开文件,如果写入 unicode 字符串,Python 会获取当前默认的编码,然后以此种编码把字符串写入文件。可惜的是,“默认编码”只是 Python 自己默认的,在 Objects/unicodeobject.c 中,用一个全局变量 unicode_default_encoding 来表示默认的编码:
/* Default encoding to use and assume when NULL is passed as encoding
parameter; it is initialized by _PyUnicode_Init().
Always use the PyUnicode_SetDefaultEncoding() and
PyUnicode_GetDefaultEncoding() APIs to access this global.
*/
static char unicode_default_encoding[100 + 1] = "ascii";
这个值在 Python 中有C接口(PyUnicode_SetDefaultEncoding)去改变,但可惜的是没有 Python 层的接口。
所以在调用到类似 file.write(some_string) 的时候,首先会有这样的编码过程:
codecs.encode(some_string, coding, errors='restrict')
当你的 some_string 是一个 unicode 字符串,并且包含 ascii 字符串不存在的字符时,就会抛出一个 UnicodeEncodeError。
所以,为了解决这个问题,可以这么处理:
(1) 在 Windows shell 下面:
>>> print some_string.encode('gbk', errors='ignore') # replace 也可,只要不是 restrict
(2) 在写入文件时,两种方法:
a. 忽略不存在的字符,同(1)
>>> f = open(filename, 'w')
>>> f.write(some_string.encode('gbk', errors='ignore')
b. byte 方式写入:
>>> f = open(filename, 'wb')
>>> f.write(some_string.encode('utf-8'))
a 会损失字符,b 不会。
另外,\uXXXX 的方式不能算错误,
https://docs.python.org/2/reference/lexical_analysis.html#literals
\uxxxx Character with 16-bit hex value xxxx (Unicode only)
\Uxxxxxxxx Character with 32-bit hex value xxxxxxxx (Unicode only)
另外,欢迎拥抱 Python 3,在 open() 时可以直接传入 encoding,彻底解决你的后顾之忧。