Python2字符编码问题小结
Stack Overflow - Why does Python print unicode characters when the default encoding is ASCII?
理论
编码中的Unicode和UTF-8
Unicode
是字符集,UTF-8
是Unicode
的一种编码方式,并列的还包括UTF-16
、UTF-32
等。
某个字符的Unicode
通过查询标准得到,其UTF-8
编码由Unicode
码计算得到。
Python2中的str和unicode
str
和unicode
是两个不同的类。
str
存储的是已经编码后的字节序列,输出时看到每个字节用16进制表示,以\x
开头。每个汉字会占用3个字节的长度。
>>> a = '啊哈哈'
>>> type(a)
<type 'str'>
>>> a
'\xe5\x95\x8a\xe5\x93\x88\xe5\x93\x88'
>>> len(a)
9
>>> a[2]
'\x8a'
unicode
是“字符”串,存储的是编码前的字符,输出是看到字符以\u
开头。每个汉字占用一个长度。定义一个Unicode
对象时,以u
开头。
>>> b = u'哟呵呵'
>>> type(b)
<type 'unicode'>
>>> b
u'\u54df\u5475\u5475'
>>> len(b)
3
>>> b[2]
u'\u5475'
str
可以通过decode()
方法转化为unicode
对象,参数指明编码方式。
>>> a.decode('utf-8')
u'\u554a\u54c8\u54c8'
unicode
可以通过encode()
方法转化为str
对象,参数指明编码方式。
>>> b.encode('utf-8')
'\xe5\x93\x9f\xe5\x91\xb5\xe5\x91\xb5'
默认编码
Python2中的默认编码,有多个不同的变量。
代码文件开头的
coding
# -*- coding: utf-8 -*-
或
# coding=utf-8
指明代码文件中的字符编码,用于代码文件中出现中文的情况。
% cat hello.py #! /usr/bin/env python # coding=utf-8 print '泥壕' % python hello.py 泥壕
如果不设置,默认是
ascii
,当出现中文字符时就不能正常识别。% cat hello.py #! /usr/bin/env python print '泥壕' % python hello.py File "hello.py", line 2 SyntaxError: Non-ASCII character '\xe6' in file hello.py on line 2, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
sys.stdin.encoding
和sys.stdout.encoding
sdtin
和stdout
输入输出使用的编码,包命令行参数和print
输出,由locale
环境变量决定。在
en_US.UTF-8
的系统中,默认值是UTF-8
。sys.getdefaultencoding()
文件读写和字符串处理等操作使用的默认编码。
默认值是
ascii
。
字符串拼接
unicode
和str
类型通过+
拼接时,输出结果是unicode
类型,相当于先将str
类型的字符串通过decode()
方法解码成unicode
,再拼接。此时如果解码时没有明确指明编码类型,可能会出现错误。
>>> a = '啊哈哈'
>>> b = u'哟呵呵'
>>>
>>> a + b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)
>>>
>>> a.decode('utf-8') + b
u'\u554a\u54c8\u54c8\u54df\u5475\u5475'
错误提到'ascii' codec can't decode byte 0xe5
,这是因为自动将str
类型的变量按照默认的编码格式sys.getdefaultencoding()
来解码,默认编码即ascii
,而这个字符不在ascii
的范围内,就出现了错误。
>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>> sys.setdefaultencoding('utf-8')
>>>
>>> a = '啊哈哈'
>>> b = u'哟呵呵'
>>> a + b
u'\u554a\u54c8\u54c8\u54df\u5475\u5475'
文件读取和json解析
读文件得到的结果是str
类型,以\x
开头的十六进制表示。
>>> f = open('t.txt')
>>> a = f.read()
>>> a
'{"hello":"\xe5\x92\xa9"}\n'
而经过json解析后会自动转为unicode
。
>>> json.loads(a)
{u'hello': u'\u54a9'}
输出
输出到文件
str
类型可以输出到文件,而unicode
类型必须先编码成str
。
>>> a = '啊哈哈'
>>> b = u'哟呵呵'
>>> a
'\xe5\x95\x8a\xe5\x93\x88\xe5\x93\x88'
>>> b
u'\u54df\u5475\u5475'
>>>
>>> f = open('t.txt', 'w')
>>> f.write(a)
>>> f.write(b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)
>>> f.write(b.encode('utf-8'))
unicode
输出到文件时的错误是由于默认编码为ascii
,无法自动完成编码过程。如果将sys.getdefaultencoding()
编码设置成了utf-8
就可以自动完成转换过程了。
>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>> sys.setdefaultencoding('utf-8')
>>>
>>> f.write(b)
计算md5
同样,md5计算也要求输入的unicode
先编码。
>>> a = '啊哈哈'
>>> b = u'哟呵呵'
>>> import hashlib
>>> hashlib.md5(a).hexdigest()
'f38b302e2993ec3fdad79c4d76074b21'
>>> hashlib.md5(b).hexdigest()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)
>>> hashlib.md5(b.encode('utf-8')).hexdigest()
'c02dc06719bafeaf60505b11d3c0c90a'
输出到stdout
输出到stdout
时,默认编码是sys.stdout.encoding
,默认值取决于系统环境变量,所以print
输出汉字时才可以不用指定utf-8
。
>>> import sys
>>> sys.stdout.encoding
'UTF-8'
>>> print u'\u54a9'
咩
而在zh_CN.GB2312
的环境中,默认值不是utf-8
,就不能正常输出了。
>>> import sys
>>> sys.stdout.encoding
'ANSI_X3.4-1968'
>>> print u'\u54a9'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u54a9' in position 0: ordinal not in range(128)
命令行参数读取
通过sys.argv
或argparse
得到的命令行参数都是编码后的str
类型,以\x
开头的十六进制表示。可以通过sys.stdin.encoding
得到命令行传入的编码类型,解码成unicode
。
#! /usr/bin/env python
# coding = utf-8
import sys
print repr(sys.argv[1])
print sys.stdin.encoding
print repr(sys.argv[1].decode(sys.stdin.encoding))
输出结果。
~/workspace % python hello.py "哇嘿嘿"
'\xe5\x93\x87\xe5\x98\xbf\xe5\x98\xbf'
UTF-8
u'\u54c7\u563f\u563f'
如果命令行环境已经改成GB2312
等其他编码,python找不到与之匹配的编码类型,就会将默认编码sys.stdin.encoding
设置成ascii
,无法通过这种方法正常解码成unicode
。
带\u的字符串转unicode
可能会遇到汉字被转换成unicode编码的形式表示的情况,即一个汉字被表示成了\u????
的形式。
>>> a = u'咩'
>>> a
u'\u54a9'
>>> b = '\u54a9'
>>> b
'\\u54a9'
上述b
就是这样的情况。此时b
是一个长度为6的字符串,而不是一个汉字。
要把b
表示为汉字编码有两种方法。
unicode-escape编码。
>>> unicode(b, 'unicode-escape') u'\u54a9'
或
>>> b.decode('unicode-escape') u'\u54a9'
eval拼接。
>>> eval('u"' + b.replace('"', r'\"')+'"') u'\u54a9'