一、背景說明
最開始不願意使用Python,一大原因是因為Python2默認使用ASCII編碼處理中文可以說是一件痛苦的事情。僅從更換默認編碼一項變換,就可以說Python3和Python2不算同一門語言。
Python3更換為默認使用Unicode(utf-8)編碼,一直使用下來再沒有遇到編碼問題帶來的困撓,似乎編碼問題在Python3時代就該完全消失的。但這兩天遇到了一個問題。
在調用一個庫時,出現了一個異常報錯類似如UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128),幾經排查之下發現只要該庫返回結果包含中文,我這邊使用print()打印該結果時就會出現該異常。
二、原因分析
2.1 數據要經過編碼才能傳輸
我們知道數據在網絡上傳輸時,需要先編碼;平時我們可能並不注意,但現在要明確,編碼的原因不在於網絡而在於傳輸。
print()相當於把字符串從內存傳輸到了tty上,所以print()是需要encode()動作的;平時我們print()時一般都不需要encode(),只是因為當print()檢測到傳來的參數是不是byte類型時自動進行了編碼。
2.2 print()使用何種編碼
Python3默認使用的是utf-8,這可以通過sys.getdefaultencoding()進行確認。但這只是默認,當系統配置了LC_ALL、LC_CTYPE、LANG等環境變量時(三者優先級從高到低),Python3采用這些變量配置的編碼;如果這些變量配置的是utf-8那Python3用的就還是utf-8,但如果不是utf-8那Python3所用的也就不是utf-8了。
當前使用的編碼可通過sys.getfilesystemencoding()獲取。
三、場景復現
為簡單起見,我們這里直接以打印一個中文字符串作為演示,示例代碼如下(我這里保存成test_encode.py):
import sys class TestEncode(): def __int__(self): pass def main_logic(self): # 打印語言默認編碼 print(f"defaultencoding--{sys.getdefaultencoding()}") # 打印系統配置的編碼 print(f"filesystemencoding--{sys.getfilesystemencoding()}") # 最后嘗試打印中文 print("中文") if __name__ == "__main__": obj = TestEncode() obj.main_logic()
shell依次執行如下命令:
# 查看當前編碼情況 locale # 確認在utf-8情況下打印中文無誤 python3 test_encode.py # 設置LC_TYPE,C代表ASCII export LC_CTYPE="C" # 查看當前編碼情況 locale # 再次運行,確認系統編碼已改變,並出現編碼錯誤 python3 test_encode.py
最終結果如下,在系統編碼配置為utf-8時打印正常,在系統編碼改為C(即ASCII)后打印報編碼異常(不過我在root用戶環境修改編碼一直不成功,不懂我電腦有點問題還是什么原因):
四、解決辦法
其實追根究底,打印報錯本質原因就是標准輸出的編碼不支持要打印的字符,對中文而言就是不是utf8(當然要說的話還可以是gbk這些),那解決辦法就是去把標准輸出的編碼給設成utf8就完事了。
那各語言的標准輸出編碼由什么決定呢,一般是語言底層根據一些自己的變量去設置標准輸出(python是PYTHONENCODING等幾個),如果沒有那就取系統的LC_ALL等配置編碼的值去設置標准輸出。
那基於這個事實,我們就有了以下三種解決辦法。
方法一:自己臨時自行修改標准輸出為utf-8
這種方法最保險,能確認自己不被覆蓋;但如果是一個項目那得確保在一開始設置,不然每次print前設置一次也很麻煩。
import sys
import codecs sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach()) print("中文")
方法二:設置語言會采用的的環境變量PYTHONIOENCODING
這種方法按理感覺有時有bug,該環境變量在一些解析器中沒被采用。
PYTHONIOENCODING=utf-8 python test_encode.py
方法三:設置語言會采用的系統環境變量值為utf-8
這種方法各種語言應該都通用。
如果配了變量仍有報錯,那一般都是運行的代碼沒正確讀取變量所致,可以在腳本中獲取下這變量值確認,python是os.environ。
# 優先級 LC_ALL > LC_*(包括決定月分顯示語言的LC_TIME等)> LANG export LC_ALL="en_US.utf8"
參考:
https://blog.csdn.net/th_num/article/details/80685389
https://timothyqiu.com/archives/surrogateescape-in-python-3/