python中文編碼亂碼問題
阿新 • • 發佈:2019-02-02
背景
多次被python的編碼/亂碼問題困擾,相信pythoner們都被困擾過,網上鋪天蓋地的資料太多也參差不齊,就整理了下。本文從使用的角度系統總結了python編碼相關的一些概念,將本文的例子玩一遍,基本上對python的編碼問題就清楚了。
首先明確幾個概念:
- 位元組流:以utf8/gbk等編碼編碼的位元組流。
- unicode物件:python程式碼中,
a=u'中國'
, 或者a='中國'.decode()
的結果。 - terminal用於顯示字元的編碼:將一個用utf8/gbk編碼的位元組流通過terminal指定的編碼,去查詢對應的字元顯示出來。
- locale:linux下,Locale 是軟體在執行時的語言環境, 它包括語言(Language), 地域 (Territory) 和字符集(Codeset)。一個locale的書寫格式為: 語言[_地域[.字符集]]. 所以說呢,locale總是和一定的字符集相聯絡的。比如:zh_CN.GB2312
- 編碼轉換原則:unicode是”中介”,任何編碼之間轉換都需要先decode()到unicode。
針對python,先把結論放在前面,三點:
- #coding:utf-8 #.py檔案是什麼編碼就需要告訴python用什麼編碼去讀取這個.py檔案。
- sys.stdout.encoding,預設就是locale的編碼,print會用sys.stdout.encoding去encode()成位元組流,交給terminal顯示。所以locale需要與terminal一致,才能正確print打印出中文。
- sys.setdefaultencoding(‘utf8’),用於指定str.encode() str.decode()的預設編碼,預設是ascii。
- 對編碼字串a,程式碼中可以直接寫a.encode(“gbk”),但事實上內部自動先通過defaultencoding 去decode成unicode之後再encode()的。
- str(xxx)應該也是用這個去編碼的。
'ascii' codec can't encode characters in position 7-8: ordinal not in range(128)
print的時候出現這個錯誤一般可以使用這個方案去處理。- 為了避免程式碼中到處都要去encode(“xxx”),還有可能不同的地方寫得不一樣帶來不一致的情況,推薦使用這個:
import sys
reload(sys)
sys.setdefaultencoding('utf8')
例子1:
- 在python中,unicode vs 位元組流:位元組流可以從unicode encode得到,unicode可以從utf8/gbk等編碼的位元組流decode得到。
- 分析下面這段程式碼,終端/locale分別為不同編碼的情況:
#coding:utf-8 #由於.py檔案是utf-8的,所以必須有這一句
import sys
import locale
import os
import codecs
reload(sys)
print sys.getdefaultencoding() + " - sys.getdefaultencoding()"
sys.setdefaultencoding('utf8') #影響encode()
print sys.getdefaultencoding() + " - sys.getdefaultencoding()"
print sys.stdout.encoding + " - sys.stdout.encoding:"
#sys.stdout = codecs.getwriter('utf8')(sys.stdout) #影響print
print sys.stdout.encoding + " - sys.stdout.encoding:"
u = u'中國'
print u + " - u"
a = '中國'
print a + " - a"
print a.decode('utf-8') + " - a.decode('utf-8')"
print a.decode('utf-8').encode('gbk') + " - a.decode('utf-8').encode('gbk')"
print a.decode('utf-8').encode('utf-8') + " - a.decode('utf-8').encode('utf-8')"
print a.decode('utf-8').encode() + " - a.decode('utf-8').encode()"
print (sys.stdout.encoding) + " - (sys.stdout.encoding)"
print (sys.stdout.isatty())
print (locale.getpreferredencoding())
print (sys.getfilesystemencoding())
—終端為UTF-8,locale為zh_CN.GBK—————–
ascii - sys.getdefaultencoding()
utf8 - sys.getdefaultencoding()
GBK - sys.stdout.encoding:
GBK - sys.stdout.encoding:
�й� - u
中國 - a
�й� - a.decode('utf-8')
�й� - a.decode('utf-8').encode('gbk')
中國 - a.decode('utf-8').encode('utf-8')
中國 - a.decode('utf-8').encode()
GBK - (sys.stdout.encoding)
True
GBK
utf-8
—終端為UTF-8,locale為zh_CN.UTF-8—————–
ascii - sys.getdefaultencoding()
utf8 - sys.getdefaultencoding()
UTF-8 - sys.stdout.encoding:
UTF-8 - sys.stdout.encoding:
中國 - u
中國 - a
中國 - a.decode('utf-8')
�й� - a.decode('utf-8').encode('gbk')
中國 - a.decode('utf-8').encode('utf-8')
中國 - a.decode('utf-8').encode()
UTF-8 - (sys.stdout.encoding)
True
UTF-8
utf-8
—終端為GBK,locale為zh_CN.GBK—————–
ascii - sys.getdefaultencoding()
utf8 - sys.getdefaultencoding()
GBK - sys.stdout.encoding:
GBK - sys.stdout.encoding:
中國 - u
涓???? - a
中國 - a.decode('utf-8')
中國 - a.decode('utf-8').encode('gbk')
涓???? - a.decode('utf-8').encode('utf-8')
涓???? - a.decode('utf-8').encode()
GBK - (sys.stdout.encoding)
True
GBK
utf-8
—終端為GBK,locale為zh_CN.UTF-8—————–
ascii - sys.getdefaultencoding()
utf8 - sys.getdefaultencoding()
UTF-8 - sys.stdout.encoding:
UTF-8 - sys.stdout.encoding:
涓???? - u
涓???? - a
涓???? - a.decode('utf-8')
中國 - a.decode('utf-8').encode('gbk')
涓???? - a.decode('utf-8').encode('utf-8')
涓???? - a.decode('utf-8').encode()
UTF-8 - (sys.stdout.encoding)
True
UTF-8
utf-8
例子1總結,對print而言:
- unicode的資料如果要顯示正常,必須終端與locale一致。sys.stdout.encoding這個值應該來自locale,print會以sys.stdout.encoding去encode並輸出到位元組流。
- encode為終端編碼的位元組流就能顯示正常,無論locale是啥。
最終是terminal通過terminal配置的編碼規則去解碼成對應的字元並顯示出來。
例子2:
關於sys.setdefaultencoding(‘utf8’)的例子:
#coding:utf-8
import sys
reload(sys)
sys.setdefaultencoding('utf8')
print sys.getdefaultencoding() + " - sys.getdefaultencoding()"
a = '中國'
print a + " - a"
print a.encode("gbk") #並不是直接從utf8的位元組流轉化到gbk的,而是通過defaultencoding decode之後才轉的。
print a.decode() #使用預設的defaultencoding
print a.encode() #使用預設的defaultencoding
關於str()和repr()
- str()是對各種型別轉化成str,如果本來是encoded字串,則不變,如果為unicode,會encode()
- repr()對字串是將位元組流出二進位制的值以16進位制轉化為可見字元。
測試環境locale為GBK
#coding:utf-8
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
a = u'中國'
print a
print str(a)
print repr(a)
print repr(a.encode("utf-8"))
print repr(a.encode("gbk"))
中國
涓????
u'\u4e2d\u56fd'
'\xe4\xb8\xad\xe5\x9b\xbd'
'\xd6\xd0\xb9\xfa'
再深挖下去,還有repr()和eval()的關係,就不深挖了。
關於終端和伺服器的編碼
另外補充一些關於終端和伺服器編碼的結論:
1. 對mac iterm2,如果server的locale與mac本地終端的locale一致,才能保證server端與本地的表現一致。
2. cat a.py #就把檔案顯示出來,就是給terminal一串位元組流。terminal根據設定的終端編碼規則來顯示字元。所以只要檔案編碼與terminal一致即可,與locale無關。
3. cat a.txt > b.txt #無論locale怎麼樣,只跟a.txt原來的編碼相關
4. echo “中國年過” > a.txt #這個情況下,只有terminal與locale的編碼一致,你才能在終端shell打出正確的中文~~~所以a.txt與兩者都會一致