1. 程式人生 > >談談python字元編碼問題

談談python字元編碼問題

ascii編碼


ASCII可以編碼英文字元,0-9數字,以及一些可列印字元,不可列印字元等。
在那個時候,編寫程式只使用這個編碼就足夠,一個字元通過寫入後,在記憶體中儲存為其對應的ASCII編號,當從記憶體中讀出時,把對應的ASCII編號轉換成對應的字元即可。

UNICODE編碼


等到各個國家開始使用計算機後,就有一個問題,如何將本國文字也可以在計算中儲存和顯示?

ASCII由於最開始只用一個位元組後7位,可以表示128個字元,但是世界上的國家那麼多,文字又千奇百怪,怎麼表示這些所有的字元。這個就產生了unicode編碼。unicode把世界上所有的字元進行了編號。因為字元數量很多,所以unicode編碼的編號需要用到到2個,3個甚至更多的位元組。現在對應的字元編號有了。接下來,如果我們輸入一個字元,是不是直接就用這個字元對應的unicode編號進行儲存呢?

例如:
漢字嚴的 Unicode 是十六進位制編號4E25,轉換成二進位制數足足有15位(100111000100101),如果我們直接用這個編號儲存在計算機上就會有一個問題,當我們讀出來的時候,我們怎麼知道他是用兩個位元組表示一個字元,而不是使用的ascii編碼,表示兩個字元,也可能表示(‘N%’)。所以我們需要一種編碼方式可以使得從記憶體中讀出結果,能知道是兩個位元組表示一個還是兩個字元。這就是utf8編碼方式。

網際網路的普及,強烈要求出現一種統一的編碼方式。UTF-8 就是在網際網路上使用最廣的一種 Unicode 的實現方式。其他實現方式還包括 UTF-16(字元用兩個位元組或四個位元組表示)和 UTF-32(字元用四個位元組表示),不過在網際網路上基本不用。重複一遍,這裡的關係是,UTF-8 是 Unicode 的實現方式之一。

UTF-8 最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個位元組表示一個符號,根據不同的符號而變化位元組長度。

UTF-8 的編碼規則很簡單,只有二條:
1)對於單位元組的符號,位元組的第一位設為0,後面7位為這個符號的 Unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的。

2)對於n位元組的符號(n > 1),第一個位元組的前n位都設為1,第n + 1位設為0,後面位元組的前兩位一律設為10。剩下的沒有提及的二進位制位,全部為這個符號的 Unicode 碼。

下表總結了編碼規則,字母x表示可用編碼的位。

Unicode符號範圍(十六進位制) UTF-8編碼方式(二進位制)
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

這裡要說明一下字符集,編碼方式的概念。
ASCII既是一種編碼集,代表著字元對應的編碼,也表示一種編碼方式,在計算機中通過ascii編碼的方式儲存字元。
unicode是一種編碼集,代表著字元對應的編碼。但是在字元卻不是直接把unicode的編碼存在計算機上,而是通過utf8這種編碼方式儲存在計算機上。

下面來看一下python中字元編碼


s1是string object,s2 是 unicode object。

>>> s1='嚴'
>>> s2=u'嚴'

可以看到在不同物件中,儲存的編碼是不同的,string object儲存的是"嚴"通過utf8進行編碼的形式(因為我是linux系統,預設是utf8編碼,如果是windows,可能會是其他編碼如GBK等),而unicode object中儲存的是"嚴"對應的unicode編碼。

>>> s1
'\xe4\xb8\xa5'
>>> s2
u'\u4e25'

通過print 可以將兩種字元都以漢字的形式輸出出來。如何做到的,string object通過chardet.detect可以知道s1的編碼型別,通過編碼型別就可以找到對應的unicode編號,找到對應的漢字。而unicode object直接通過編碼就可以找到漢字。

>>> chardet.detect(s1)
{'confidence': 0.505, 'encoding': 'utf-8'}
>>> print s1
嚴
>>> print s2
嚴

如果把s1和s2放在list裡面,可以輸出對應的編碼,但是已經無法輸出漢字了,這是因為l1和l2已經不是字串了,print也無法輸出漢字

>>> l1=[s1]
>>> l2=[s2]
>>> l1
['\xe4\xb8\xa5']
>>> l2
[u'\u4e25']
>>> print l1
['\xe4\xb8\xa5']
>>> print l2
[u'\u4e25']

如果用json.dumps或者str後呢,可以看到dumps之後list中的字元都變成了unicode編碼,即原來用utf8編碼記錄的list會變為用unicode編碼的;而str則不會改變編碼方式。print效果一樣。

>>> json.dumps(l1)
'["\\u4e25"]'
>>> json.dumps(l2)
'["\\u4e25"]'
>>> str(l1)
"['\\xe4\\xb8\\xa5']"
>>> str(l2)
"[u'\\u4e25']"
>>> print str(l1)
['\xe4\xb8\xa5']
>>> print str(l2)
[u'\u4e25']
>>> print json.dumps(l1)
["\u4e25"]
>>> print json.dumps(l2)
["\u4e25"]

如果要顯示中文

>>> print json.dumps(l1, ensure_ascii=False)
["嚴"]
>>> print json.dumps(l2, ensure_ascii=False)
["嚴"]

字典也可以使用這種方法顯示中文

>>> d1= {'a' : s1}
>>> d2= {'a' : s2}
>>> print d1
{'a': '\xe4\xb8\xa5'}
>>> print d2
{'a': u'\u4e25'}
>>> print json.dumps(d1)
{"a": "\u4e25"}
>>> print json.dumps(d2)
{"a": "\u4e25"}
>>> print json.dumps(d1,ensure_ascii=False)
{"a": "嚴"}
>>> print json.dumps(d2,ensure_ascii=False)
{"a": "嚴"}

備註:
有文章說json顯示中文需要如下方式:

json.dumps(m,ensure_ascii=False).decode('utf8').encode('gb2312')

這是因為window平臺預設的中文編碼編碼方式是GBK,所以,在使用json.dumps(m,ensure_ascii=False) 時,該函式執行的結果為字元按照utf8方式的編碼,print時會把utf8的編碼使用GBK進行解碼,所以出現亂碼。因此先要使用utf8解碼,再使用GBK編碼,這樣在print時,使用預設的GBK解碼方式就可以輸出正確的中文了。

window平臺:

# -*- coding: utf-8 -*-
m = {'a' : '你好'}

print m
=>{'a': '\xe4\xbd\xa0\xe5\xa5\xbd'}

print json.dumps(m)
=>{"a": "\u4f60\u597d"}

print json.dumps(m,ensure_ascii=False)
=>{"a": "浣犲ソ"}

print json.dumps(m,ensure_ascii=False).decode('utf8').encode('gb2312')
=>{"a": "你好"}

ujson與json 模組的對比

使用ujson 輸出含有中文的json字串,分別使用%s和format兩種方法。

# -*- coding: utf-8 -*-
import ujson as json
#import json
js_m = '''{"a" : "木馬行為 - 告警次數超過閾值", "b":"10.10.10.10"}'''

m = json.loads(js_m)

#print alertDict

#print json.dumps(m)
try:
    print "result:'%s' and '%s'" %(json.dumps(m['b']), json.dumps(m, ensure_ascii=False))
except Exception,e:
    print e
print type(json.dumps(m, ensure_ascii=False))
print "result:'{}' and '{}'".format(m['b'],json.dumps(m, ensure_ascii=False).encode('utf8'))
print "result:'{}' and '{}'".format(str(m['b']),json.dumps(m, ensure_ascii=False))
print "result:'{}' and '{}'".format(json.dumps(m['b']),json.dumps(m, ensure_ascii=False))

結果:

➜  ~ python utf2.py
result:'"10.10.10.10"' and '{"a":"木馬行為 - 告警次數超過閾值","b":"10.10.10.10"}'
<type 'str'>
Traceback (most recent call last):
  File "utf2.py", line 16, in <module>
    print "result:'{}' and '{}'".format(m['b'],json.dumps(m, ensure_ascii=False).encode('utf8'))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 6: ordinal not in range(128)

format輸出錯誤,因為json.dumps(m, ensure_ascii=False)的結果為str,可以使用chardet.detect檢視他的編碼格式,發現它是utf8的編碼,所以這裡不能在使用encode(‘utf8’)。
修改為:

# -*- coding: utf-8 -*-
import ujson as json
#import json
js_m = '''{"a" : "木馬行為 - 告警次數超過閾值", "b":"10.10.10.10"}'''

m = json.loads(js_m)

#print alertDict

#print json.dumps(m)
try:
    print "result:'%s' and '%s'" %(json.dumps(m['b']), json.dumps(m, ensure_ascii=False))
except Exception,e:
    print e
print type(json.dumps(m, ensure_ascii=False))
print "result:'{}' and '{}'".format(m['b'],json.dumps(m, ensure_ascii=False))
print "result:'{}' and '{}'".format(str(m['b']),json.dumps(m, ensure_ascii=False))
print "result:'{}' and '{}'".format(json.dumps(m['b']),json.dumps(m, ensure_ascii=False))

結果:

➜  ~ python utf.py
result:'"10.10.10.10"' and '{"a":"木馬行為 - 告警次數超過閾值","b":"10.10.10.10"}'
<type 'str'>
{'confidence': 0.99, 'encoding': 'utf-8'}
result:'10.10.10.10' and '{"a":"木馬行為 - 告警次數超過閾值","b":"10.10.10.10"}'
result:'10.10.10.10' and '{"a":"木馬行為 - 告警次數超過閾值","b":"10.10.10.10"}'
result:'"10.10.10.10"' and '{"a":"木馬行為 - 告警次數超過閾值","b":"10.10.10.10"}'

如果%s格式化也用三種方式,會是什麼效果:

# -*- coding: utf-8 -*-
import ujson as json
#import json
js_m = '''{"a" : "木馬行為 - 告警次數超過閾值", "b":"10.10.10.10"}'''

m = json.loads(js_m)

#print alertDict

#print json.dumps(m)
print type(m['b'])
print type(json.dumps(m, ensure_ascii=False))
print "result:'%s'" %(m['b'])

print "result:'%s'" %((json.dumps(m, ensure_ascii=False)))
print "result:'%s' and '%s'" %(m['b'], unicode(json.dumps(m, ensure_ascii=False)))
print "result:'%s' and '%s'" %(str(m['b']), json.dumps(m, ensure_ascii=False))
print "result:'%s' and '%s'" %(json.dumps(m['b']), json.dumps(m, ensure_ascii=False))

結果:

➜  ~ python utf.py
<type 'unicode'>
<type 'str'>
result:'10.10.10.10'
result:'{"a":"木馬行為 - 告警次數超過閾值","b":"10.10.10.10"}'
Traceback (most recent call last):
  File "utf.py", line 86, in <module>
    print "result:'%s' and '%s'" %(m['b'], json.dumps(m, ensure_ascii=False))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 6: ordinal not in range(128)

原因是:單獨輸出m[‘b’] 和 json.dumps(m, ensure_ascii=False) 都可以輸出,但是一起就不行了。一個是unicode型別,一個是str類,utf8編碼。使用%s這種方式無法使用同時輸出兩種型別的字元。必須同一位一種型別,要麼是str,要麼是unicode。

兩種型別一起輸出的例子:

s1=u"sssss"
s2="嚴"
print "ret:'%s' and '%s'" %(s1, s2)

結果:

➜  ~ python utf.py
Traceback (most recent call last):
  File "utf.py", line 92, in <module>
    print "ret:'%s' and '%s'" %(s1, s2)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

我們把str型別程式設計unicode型別就可以了,修改程式碼:

print "result:'%s' and '%s'" %(m['b'], unicode(json.dumps(m, ensure_ascii=False)))

修改為:

print "result:'%s' and '%s'" %(m['b'], unicode(json.dumps(m, ensure_ascii=False),'utf8'))

這樣m[‘b’] 和unicode(json.dumps(m, ensure_ascii=False),‘utf8’) 都是 unicode型別了,或者都改為str型別,那就是str(m[‘b’])和json.dumps(m[‘b’])兩種方式。

總結


  1. %s輸出方式不能同時格式化輸出不同型別的字元,必須同時都變為str型別或者unicode型別。
  2. foramt 的方式則不存在這樣的問題,可以輸出兩種不同型別的字元。
  3. ujson 和json的區別是,dumps()函式返回值,ujson是str型別,而json返回的是unicode型別。
  4. chardet.detect可以檢測str型別的編碼方式,通過這個函式可以知道str編碼型別,更系統平臺有關,linux為utf8為主,而window為GBK為主。
  5. python列表,字典型別的變數輸出中文,可以使用json.dumps(x, ensure_ascii=False)。json和ujson一樣的。
  6. 某些情況下要出中文還需要編解碼,例如windows平臺,預設是GBK編碼,使用json.dumps(x, ensure_ascii=False)返回的是unicode型別,需要先用decode(‘utf8’)解碼,再用encode(‘GBK’)編碼才能正確輸出中文字元。

最後再寫一個有意思的C程式:

#include<stdio.h>
#include<stdlib.h>

int main(int argc, const char * argv[]) {
    char p[] = {0xe4,0xb8,0xa5};
    printf("%s\n", p);
    return 0;
}

請問他的輸出是什麼?
答案是

[[email protected] ~]$ gcc test.c
[[email protected] ~]$ ./a.out
嚴

參考:
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

https://blog.csdn.net/u014431852/article/details/53058951

https://blog.csdn.net/ktb2007/article/details/3876436

http://outofmemory.cn/code-snippet/4092/python-json-charset-type

http://python.jobbole.com/81244/

http://www.10tiao.com/html/331/201610/2651688148/1.html

https://blog.csdn.net/anlian523/article/details/80504699