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