1. 程式人生 > >python中文編碼亂碼問題

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與兩者都會一致

參考資料