Python3.3开始已经移除了这个方法,因此这个问题算是python2的问题了
http://blog.ernest.me/post/python-setdefaultencoding-unicode-bytes
最坏实践python
1
2
3
|
import
sys
reload
(sys)
sys.setdefaultencoding(
'utf-8'
)
|
上面这种代码曾经(如今依然)是解决中文编码的万能钥匙。解决编码错误问题一劳永逸,今后和 UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128) 说 byebye 。
那么如今,对于那些想解决 UnicodeEncodeError 问题而搜索到这篇文章的读者,我要说的是,不要用以上的代码片断。下面我来讲说为何,以及咱们应该怎么作。
sys.setdefaultencoding('utf-8') 会致使的两个大问题
简单来讲这么作将会使得一些代码行为变得怪异,而这怪异还很差修复,以一个不可见的 bug 存在着。下面咱们举两个例子。
1. 编码错误app
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import
chardet
def
print_string(string):
try
:
print
(u
"%s"
%
string)
except
UnicodeError:
print
u
"%s"
%
unicode
(byte_string, encoding
=
chardet.detect(string)[
'encoding'
])
print_string(u
"Æ"
.encode(
"latin-1"
))
import
sys
reload
(sys)
sys.setdefaultencoding(
'utf-8'
)
print
(key_in_dict(
'Æ'
))
|
输出:函数
1
2
|
$~ Æ
$~ Æ
|
在上面的代码中,默认的 ascii 编码没法解码,Æ latin-1 编码 hex 表示是 c5 ae ,显然是超出了只有128个字符的 ascii 码集的,引起 UnicodeError 异常,进入异常处理。异常处理则会根据编码探测,用最可能的编码来解码,会比较靠谱地输出 Æ 。
而一旦咱们将 defaultencoding 设置为 utf-8,由于 utf-8 的字符范围是彻底覆盖 latin-1,所以,会直接使用 utf-8 进行解码。c5 ae 在 utf-8 中,是 Æ。因而咱们打印出了彻底不一样的字符。
可能大家会说咱们不会写这样的代码。若是咱们写了也会作修正。但若是是第三方库这么写了呢?项目依赖的第三方库就这么 bug 了。若是你不依赖第三方库,那么下面这个 bug,仍是逃不过。
2. dictionray 行为异常
假设咱们要从一个 dictionary 里查找一个 key 是否存在,一般来讲,有两种可行方法。oop
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#-*- coding: utf-8 -*-
d
=
{
1
:
2
,
'1'
:
'2'
,
'你好'
:
'hello'
}
def
key_in_dict(key)
if
key
in
d:
return
True
return
False
def
key_found_in_dict(key):
for
_key
in
d:
if
_key
=
=
key:
return
True
return
False
|
咱们对比下改变系统默认编码先后这俩函数的输出有什么不一样。post
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#-*- coding: utf-8 -*-
print
(key_in_dict(
'你好'
))
print
(key_found_dict(
'你好'
))
print
(key_in_dict(u
'你好'
))
print
(key_found_in_dict(u
'你好'
))
print
(
'------utf-8------'
)
import
sys
reload
(sys)
sys.setdefaultencoding(
'utf-8'
)
print
(key_in_dict(
'你好'
))
print
(key_found_dict(
'你好'
))
print
(key_in_dict(u
'你好'
))
print
(key_found_in_dict(u
'你好'
))
|
输出:编码
1
2
3
4
5
6
7
8
9
|
$~
True
$~
True
$~
False
$~
False
$~
-
-
-
-
-
-
utf
-
8
-
-
-
-
-
-
$~
True
$~
True
$~
False
$~
True
|
能够看到,当默认编码改了以后,两个函数的输出再也不一致。
dict 的 in 操做符将键作哈希,并比较哈希值判断是否相等。对于 ascii 集合内的字符来讲,不论是字节字符类型仍是仍是 unicode 类型,其哈希值是同样的,如 u'1' in {'1':1} 会返回 True,而超出 ascii 码集的字符,如上例中的 '你好',它的字节字符类型的哈希与 unicode 类型的哈希是不同的。
而 == 操做符则是作了一次转换,将字节字符(byte string,上面的 '你好')转换成 unicode(u'你好') 类型,而后对转换后的结果作比较。在 ascii 系统默认编码中,'你好'转换成 Unicode 会产生 Warning: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal,由于超出码集没法转换,系统会默认其不相等。当系统编码被咱们手动改成 utf-8 后,这个禁忌则被解除,'你好' 可以顺利被转换成 unicode,最后的结果就是,in 和 == 行为再也不一致。
问题的根源:Python2 中的 string
Python 为了让其语法看上去简洁好用,作了不少 tricky 的事情,混淆 byte string 和 text string 就是其中一例。
在 Python 里,有三大类 string 类型,unicode(text string),str(byte string,二进制数据),basestring,是前二者的父类。
其实,在语言设计领域,一串字节(sequences of bytes)是否应该当作字符串(string)一直是存在争议的。咱们熟知的 Java 和 C# 投了反对票,而 Python 则站在了支持者的阵营里。其实咱们在不少状况下,给文本作的操做,好比正则匹配、字符替换等,对于字节来讲是用不着的。而 Python 认为字节就是字符,因此他们俩的操做集合是一致的。
而后进一步的,Python 会在必要的状况下,尝试对字节作自动类型转换,例如,在上文中的 ==,或者字节和文本拼接时。若是没有一个编码(encoding),两个不一样类型之间的转换是没法进行的,因而,Python 须要一个默认编码。在 Python2 诞生的年代,ASCII 是最流行的(能够这么说吧),因而 Python2 选择了 ASCII。然而,众所周知,在须要须要转换的场景,ASCII 都是没用的(128个字符,够什么吃)。
在历经这么多年吐槽后,Python 3 终于学乖了。默认编码是 Unicode,这也就意味着,作全部须要转换的场合,都能正确并成功的转换。
最佳实践
说了这么多,若是不迁移到 Python 3,能怎么作呢?
有这么几个建议:
全部 text string 都应该是 unicode 类型,而不是 str,若是你在操做 text,而类型倒是 str,那就是在制造 bug。
在须要转换的时候,显式转换。从字节解码成文本,用 var.decode(encoding),从文本编码成字节,用 var.encode(encoding)。
从外部读取数据时,默认它是字节,而后 decode 成须要的文本;一样的,当须要向外部发送文本时,encode 成字节再发送。spa