python中文編碼亂碼問題

背景

多次被python的編碼/亂碼問題困擾,相信pythoner們都被困擾過,網上鋪天蓋地的資料太多也參差不齊,就整理了下。本文從使用的角度系統總結了python編碼相關的一些概念,將本文的例子玩一遍,基本上對python的編碼問題就清楚了。

首先明確幾個概念:

  1. 字節流:以utf8/gbk等編碼編碼的字節流。
  2. unicode對象:python代碼中,a=u'中國', 或者a='中國'.decode()的結果。
  3. terminal用於顯示字符的編碼:將一個用utf8/gbk編碼的字節流通過terminal指定的編碼,去查找對應的字符顯示出來。
  4. locale:linux下,Locale 是軟件在運行時的語言環境, 它包括語言(Language), 地域 (Territory) 和字符集(Codeset)。一個locale的書寫格式爲: 語言[_地域[.字符集]]. 所以說呢,locale總是和一定的字符集相聯繫的。比如:zh_CN.GB2312
  5. 編碼轉換原則:unicode是”中介”,任何編碼之間轉換都需要先decode()到unicode。

針對python,先把結論放在前面,三點:

  1. #coding:utf-8 #.py文件是什麼編碼就需要告訴python用什麼編碼去讀取這個.py文件。
  2. sys.stdout.encoding,默認就是locale的編碼,print會用sys.stdout.encoding去encode()成字節流,交給terminal顯示。所以locale需要與terminal一致,才能正確print打印出中文。
  3. 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與兩者都會一致

參考資料

關於vim:http://blog.chinaunix.net/uid-21843387-id-106001.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章