徹底解決Python編碼問題

發表于:2020-4-26 09:15  作者:oaksharks   來源:博客園精華區

字體: | 上一篇 | 下一篇 |我要投稿 | 推薦標簽: 軟件開發 Python

  1. 基本概念
  字符集(Character set)
  解釋:文字和符合的總稱
  常見字符集:
  Unicode字符集
  ASCII字符集(Unicode子集)
  GB2312字符集
  編碼方法(Encoding)
  解釋:將字符對應到字節的方法,部分字符集和編碼方法名稱一樣。
  常見編碼方法:
  UTF-8:可對Unicode字符進行編碼
  GB2312
  ASCII
  編碼(Encode)
  解釋:將字符集中字符按照一定規則轉換成字節
  解碼(Decode)
  解釋:與編碼相反,將字節轉換為字符集中的字符
  字符集與編碼方法的關系
  每個字符集都有對應的編碼方法
  一種字符集可能有多種編碼方法
  不同的編碼方法得到的字節不同,占用存儲空間也不一樣
  例如Unicode字符可以使用UTF-8/ASCII/GBK等方法編碼
  Unicode字符集包含世界上大部分字符,很多其他字符集有的字符它都有,是他們的超集
  大部分字符集可以理解為Unicode的子集
  實際上,除了Unicode之外所謂的字符集主要是對Unicode部分字符編碼而已(定義編碼方式)
  一種編碼不必支持Unicode的所有字符(通常把它能支持的那部分稱為它的字符集)
  2. 關于編碼的錯誤和解決方法
  在開發過程中,我們所接觸的字符集大多都是Unicode,大部分報錯都是關于編碼和解碼的。
  2.1. 編碼錯誤UnicodeEncodeError
  2.1.1. 錯誤分析
  導致該錯誤的原因通常是編碼方法支持的Unicode字符不全;在工作中,你寫了一個txt中文文檔,想用ascii編碼去保存這個文件,就會報這種錯誤。
  錯誤復現:
  我們知道 ascii 不支持字符 中 ,那我們用 ascii 編碼方法對 Unicode 碼 中 進行編碼:
# -*- encoding: utf-8 -*-
u"中".encode(encoding='ascii')
  報錯如下:
  UnicodeEncodeError: 'ascii' codec can't encode character '\u4e2d' in position 0: ordinal not in range(128)
  這是一個 UnicodeEncodeError 類型的錯誤,提示無法使用指定的編碼方法對字符進行編碼,報錯提示中可以得到3個信息:
  當前使用的是acsii編碼方法
  被編碼的字符是'\u4e2d'
  ascii編碼方法能支持的字符有128個
  有時候我們還可以利用這個提示查看編碼方法支持的字符個數:
#-*-encoding:utf-8-*-
u"中".encode(encoding='iso-8859-1')
  報錯:
  UnicodeEncodeError: 'latin-1' codec can't encode character '\u4e2d' in position 0: ordinal not in range(256)
  通過報錯提示可以看出 iso-8859-1 能編256個字符。
  接著,我們來看下用支持中文的 utf-8 編碼方法進行編碼能得到什么結果:
# -*- encoding: utf-8 -*-
s = u"中".encode(encoding='utf-8')
print("s: ", s)
print("s == 中?" , s == '中')
print("type of s: ", type(s))
print("str==bytes? ", bytes == type(s))
  輸出:
('s: ', '\xe4\xb8\xad')
('s == \xe4\xb8\xad?', True)
('type of s: ', <type 'str'>)
('str==bytes? ', True)
  從輸出的結果可以得到:
  - 編碼得到的對象跟我們直接定義的字符串是一樣的,都是str
  - `str`就是`bytes`(python中)
  2.1.2. 解決方法
  UnicodeEncodeError 是說編碼方法支持的字符不全,而 UTF-8 編碼就能很好地對Unicode編碼,所以只要把編碼方法指定為 utf-8 就可以了。
  在python2中:
  如果你調用 encode 方法但沒有指定 encoding 參數,那很可能使用了系統默認的參數,就像:
# -*- encoding: utf-8 -*-
import sys
print "default encoding is %s ." % sys.getdefaultencoding()
u'中'.encode()
  輸出:
default encoding is ascii .
UnicodeEncodeError: 'ascii' codec can't encode character u'\u4e2d' in position 0: ordinal not in range(128)
  可以手動指定encoding參數,也可以修改python默認編碼方法:
# -*- encoding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')  # 必須在reload之后調用
print u'中'.encode()
  在python3中:
  在python3中你很難看到UnicodeEncodeError了,因為python3的默認編碼就是utf-8,而Unicode字符都可以用utf-8編碼方法編碼。
  2.2. 編碼錯誤UnicodeDecodeError
  2.2.1. 錯誤分析
  導致該錯誤的原因是使用了錯誤的解碼方式把字節數據還原成字符。例如在工作中,有一個utf-8生成中文文檔,我們選擇用ascii編碼解碼,就會報這個錯。
  錯誤復現:
  我們知道python中字符串和字節是一樣,我們可以定義一個中文字符串,通過ascii來解碼生成Unicode,復現這個錯誤:
  # -*- encoding: utf-8 -*-
  print '中'.decode(encoding='ascii')
  輸出:
  UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
  注意,這是一個 UnicodeDecodeError 錯誤,區別于編碼錯誤 UnicodeEncodeError ,它提示無法把字節 0xe4 (實際上 中 對應三字節,0xe4是第一個) 解碼成Unicode。0xe4轉換為10進制是228,已經超過127了,所以ascii無能力了。
  2.2.2. 解決方法
  我們需要知道生成字節的編碼方式才能進行還原,就好像對一個文件進行了加密,必須得知道加密的密碼才能把文件還原,而解碼方式(或者稱為編碼)就是那個密碼。 所以不管是python2還是python3直接去修改默認編碼為UTF-8不一定能解決問題,具體的方法有兩種:
  通過源碼找到解碼失敗的字節是使用那種編碼生成的
  對報錯的字節數據使用各種常見的編碼進行解碼,觀察哪一種是正確的
  以一個例子來說明為什么直接設置默認編碼為UTF-8不能有效解決 UnicodeDecodeError 問題
  python文件頭指定了編碼為,在聲明字符串時候將會使用指定的編碼轉換為字節:
# -*- encoding: GBK -*-
s = "張"
print ("s is ", s)
s_unicode = u'張'
print ("encoding with GBK is ", s_unicode.encode("GBK"))
print ("encoding with UTF-8 is ", s_unicode.encode("UTF-8"))
  輸出:
('s is ', '\xd5\xc5')
('encoding with GBK is ', '\xd5\xc5')
('encoding with UTF-8 is ', '\xe5\xbc\xa0')
  在文件頭中指定了GBK編碼后默認情況下字符 張 就會被編碼為 \xd5\xc5 ,這與我們手動用GBK編碼得到結果一致,而使用utf-8編碼得到的是3個字節的數據 \xe5\xbc\xa0 (使用了更多的存儲空間)。
  例子開始了,在python2中將一個dict轉換成json:
# -*- encoding: GBK -*-
import json
d = {'name': "張", 'sex': u'man'}
print (json.dumps(d))
  輸出:
  UnicodeDecodeError: 'utf8' codec can't decode byte 0xd5 in position 0: invalid continuation byte
  錯誤說無法使用utf-8解碼0xd5,0xd5也就是GBK中的張,我們知道這個字節是用GBK生成的,這個時候可以設置json.dumps的encoding參數解決:
# -*- encoding: GBK -*-
import json
d = {'name': "張", 'sex': u'man'}
print (json.dumps(d, encoding='gbk'))
  修改一下代碼,繼續使用我們熟悉的utf-8編碼來執行:
# -*- encoding: utf-8 -*-
import json
d = {'name': "張", 'sex': u'man'}
print (json.dumps(d, encoding='utf-8'))
  輸出:
  {"name": "\u5f20", "sex": "man"}
  發現name是unicode,使用ensure_ascii=False,不強制轉換成ascii:
# -*- encoding: utf-8 -*-
import json
d = {'name': "張", 'sex': u'man'}
print (json.dumps(d, encoding='utf-8', ensure_ascii=False))
  輸出:
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 251, in dumps
sort_keys=sort_keys, **kw).encode(obj)
File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.py", line 210, in encode
return ''.join(chunks)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 1: ordinal not in range(128)
  納尼,文件頭也是utf-8,也指定了utf-8怎么還是報錯?錯誤提示用ascii去解碼0xe5字節,在上面的代碼輸出中可以知道0xe5是utf-8對字符 張 編碼的第一個字節 ,報錯的原因是用ascii去解析utf-8生成的字節了。我們并沒有設置哪個地方使用ascii解碼,應該是系統默認的編碼,嘗試設置系統默認編碼再執行:
# -*- encoding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
import json
d = {'name': "張", 'sex': u'man'}
print (json.dumps(d, encoding='utf-8', ensure_ascii=False))
  輸出:
  {"name": "張", "sex": "man"}
  3. 總結
  3.1. python2和python3對字符串的處理區別
  Python2
  默認編碼是ascii
  Python3
  import sys;sys.setdefaultencoding('utf-8')
  3.2. 為什么不全用UTF-8編碼?
  UTF-8包含的字符更多,占用的內存和磁盤空間也更大,比如對漢字 張 ,utf-8是3個字節,gbk是2個字節。
  3.3. 如何快速解決UnicodeEncodeError錯誤?
  python3中基本不會出現,python2中嘗試設置默認編碼為utf-8。
  3.4. 快速解決UnicodeDecodeError?
  需要知道出錯的字節是使用哪種編碼方式生成的,然后嘗試把默認編碼設置成這種。

本文內容不用于商業目的,如涉及知識產權問題,請權利人聯系博為峰小編(021-64471599-8017),我們將立即處理。

【福利】填問卷送精選測試禮包+接口測試課程!為測試行業做點事!

評 論

論壇新帖

頂部 底部


建議使用IE 6.0以上瀏覽器,800×600以上分辨率,法律顧問:上海瀛東律師事務所 張楠律師
版權所有 上海博為峰軟件技術股份有限公司 Copyright©51testing.com 2003-2020, 滬ICP備05003035號
投訴及意見反饋:webmaster@51testing.com; 業務聯系:service@51testing.com 021-64471599-8017

滬公網安備 31010102002173號

51Testing官方微信

51Testing官方微博

掃一掃 測試知識全知道

日本av