也談 Python 的中文編碼處理

来源:https://www.cnblogs.com/imshome/archive/2018/01/22/8327401.html
-Advertisement-
Play Games

轉載自:http://in355hz.iteye.com/blog/1860787 最近業務中需要用 Python 寫一些腳本。儘管腳本的交互只是命令行 + 日誌輸出,但是為了讓界面友好些,我還是決定用中文輸出日誌信息。 很快,我就遇到了異常: Python代碼 UnicodeEncodeError ...


轉載自:http://in355hz.iteye.com/blog/1860787

最近業務中需要用 Python 寫一些腳本。儘管腳本的交互只是命令行 + 日誌輸出,但是為了讓界面友好些,我還是決定用中文輸出日誌信息。

 

很快,我就遇到了異常:

 

Python代碼  收藏代碼
  1. UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)  

 

為瞭解決問題,我花時間去研究了一下 Python 的字元編碼處理。網上也有不少文章講 Python 的字元編碼,但是我看過一遍,覺得自己可以講得更明白些。

 

下麵先覆述一下 Python 字元串的基礎,熟悉此內容的可以跳過。

 

對應 C/C++ 的 char 和 wchar_t, Python 也有兩種字元串類型,str 與 unicode:

 

Python代碼  收藏代碼
  1. # -*- coding: utf-8 -*-  
  2. # file: example1.py  
  3. import string  
  4.   
  5. # 這個是 str 的字元串  
  6. s = '關關雎鳩'  
  7.   
  8. # 這個是 unicode 的字元串  
  9. u = u'關關雎鳩'  
  10.   
  11. print isinstance(s, str)      # True  
  12. print isinstance(u, unicode)  # True  
  13.   
  14. print s.__class__   # <type 'str'>  
  15. print u.__class__   # <type 'unicode'>  

 

前面的申明:# -*- coding: utf-8 -*- 表明,上面的 Python 代碼由 utf-8 編碼。

 

為了保證輸出不會在 linux 終端上顯示亂碼,需要設置好 linux 的環境變數:export LANG=en_US.UTF-8

 

如果你和我一樣是使用 SecureCRT,請設置 Session Options/Terminal/Appearance/Character Encoding 為 UTF-8 ,保證能夠正確的解碼 linux 終端的輸出。

 

兩個 Python 字元串類型間可以用 encode / decode 方法轉換:

 

Python代碼  收藏代碼
  1. # 從 str 轉換成 unicode  
  2. print s.decode('utf-8')   # 關關雎鳩  
  3.   
  4. # 從 unicode 轉換成 str  
  5. print u.encode('utf-8')   # 關關雎鳩  

 

為什麼從 unicode 轉 str 是 encode,而反過來叫 decode? 

 

因為 Python 認為 16 位的 unicode 才是字元的唯一內碼,而大家常用的字元集如 gb2312,gb18030/gbk,utf-8,以及 ascii 都是字元的二進位(位元組)編碼形式。把字元從 unicode 轉換成二進位編碼,當然是要 encode。

 

反過來,在 Python 中出現的 str 都是用字元集編碼的 ansi 字元串。Python 本身並不知道 str 的編碼,需要由開發者指定正確的字元集 decode。

 

(補充一句,其實 Python 是可以知道 str 編碼的。因為我們在代碼前面申明瞭 # -*- coding: utf-8 -*-,這表明代碼中的 str 都是用 utf-8 編碼的,我不知道 Python 為什麼不這樣做。)

 

如果用錯誤的字元集來 encode/decode 會怎樣?

 

Python代碼  收藏代碼
  1. # 用 ascii 編碼含中文的 unicode 字元串  
  2. u.encode('ascii')  # 錯誤,因為中文無法用 ascii 字元集編碼  
  3.                    # UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)  
  4.   
  5. # 用 gbk 編碼含中文的 unicode 字元串  
  6. u.encode('gbk')  # 正確,因為 '關關雎鳩' 可以用中文 gbk 字元集表示  
  7.                  # '\xb9\xd8\xb9\xd8\xf6\xc2\xf0\xaf'  
  8.                  # 直接 print 上面的 str 會顯示亂碼,修改環境變數為 zh_CN.GBK 可以看到結果是對的  
  9.   
  10. # 用 ascii 解碼 utf-8 字元串  
  11. s.decode('ascii')  # 錯誤,中文 utf-8 字元無法用 ascii 解碼  
  12.                    # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  
  13.   
  14. # 用 gbk 解碼 utf-8 字元串  
  15. s.decode('gbk')  # 不出錯,但是用 gbk 解碼 utf-8 字元流的結果,顯然只是亂碼  
  16.                  # u'\u934f\u51b2\u53e7\u95c6\u5ea8\u7b2d'  

 

這就遇到了我在本文開頭貼出的異常:UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

 

現在我們知道了這是個字元串編碼異常。接下來, 為什麼 Python 這麼容易出現字元串編/解碼異常? 

 

這要提到處理 Python 編碼時容易遇到的兩個陷阱。第一個是有關字元串連接的:

 

Python代碼  收藏代碼
  1. # -*- coding: utf-8 -*-  
  2. # file: example2.py  
  3.   
  4. # 這個是 str 的字元串  
  5. s = '關關雎鳩'  
  6.   
  7. # 這個是 unicode 的字元串  
  8. u = u'關關雎鳩'  
  9.   
  10. s + u  # 失敗,UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

 

簡單的字元串連接也會出現解碼錯誤?

 

陷阱一:在進行同時包含 str 與 unicode 的運算時,Python 一律都把 str 轉換成 unicode 再運算,當然,運算結果也都是 unicode。

 

由於 Python 事先並不知道 str 的編碼,它只能使用 sys.getdefaultencoding() 編碼去 decode。在我的印象里,sys.getdefaultencoding() 的值總是 'ascii' ——顯然,如果需要轉換的 str 有中文,一定會出現錯誤。

 

除了字元串連接,% 運算的結果也是一樣的:

 

Python代碼  收藏代碼
  1. # 正確,所有的字元串都是 str, 不需要 decode  
  2. "中文:%s" % s   # 中文:關關雎鳩  
  3.   
  4. # 失敗,相當於運行:"中文:%s".decode('ascii') % u  
  5. "中文:%s" % u  # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  
  6.   
  7. # 正確,所有字元串都是 unicode, 不需要 decode  
  8. u"中文:%s" % u   # 中文:關關雎鳩  
  9.   
  10. # 失敗,相當於運行:u"中文:%s" % s.decode('ascii')  
  11. u"中文:%s" % s  # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

 

我不理解為什麼 sys.getdefaultencoding() 與環境變數 $LANG 全無關係。如果 Python 用 $LANG 設置 sys.getdefaultencoding() 的值,那麼至少開發者遇到 UnicodeDecodeError 的幾率會降低 50%。

 

另外,就像前面說的,我也懷疑為什麼 Python 在這裡不參考 # -*- coding: utf-8 -*- ,因為 Python 在運行前總是會檢查你的代碼,這保證了代碼里定義的 str 一定是 utf-8 。

 

對於這個問題,我的唯一建議是在代碼里的中文字元串前寫上 u。另外,在 Python 3 已經取消了 str,讓所有的字元串都是 unicode ——這也許是個正確的決定。

 

其實,sys.getdefaultencoding() 的值是可以用“後門”方式修改的,我不是特別推薦這個解決方案,但是還是貼一下,因為後面有用:

 

Python代碼  收藏代碼
  1. # -*- coding: utf-8 -*-  
  2. # file: example3.py  
  3. import sys  
  4.   
  5. # 這個是 str 的字元串  
  6. s = '關關雎鳩'  
  7.   
  8. # 這個是 unicode 的字元串  
  9. u = u'關關雎鳩'  
  10.   
  11. # 使得 sys.getdefaultencoding() 的值為 'utf-8'  
  12. reload(sys)                      # reload 才能調用 setdefaultencoding 方法  
  13. sys.setdefaultencoding('utf-8')  # 設置 'utf-8'  
  14.   
  15. # 沒問題  
  16. s + u  # u'\u5173\u5173\u96ce\u9e20\u5173\u5173\u96ce\u9e20'  
  17.   
  18. # 同樣沒問題  
  19. "中文:%s" % u   # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20'  
  20.   
  21. # 還是沒問題  
  22. u"中文:%s" % s  # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20'  

 

可以看到,問題魔術般的解決了。但是註意! sys.setdefaultencoding() 的效果是全局的,如果你的代碼由幾個不同編碼的 Python 文件組成,用這種方法只是按下了葫蘆浮起了瓢,讓問題變得複雜。

 

另一個陷阱是有關標準輸出的。

 

剛剛怎麼來著?我一直說要設置正確的 linux $LANG 環境變數。那麼,設置錯誤的 $LANG,比如 zh_CN.GBK 會怎樣?(避免終端的影響,請把 SecureCRT 也設置成相同的字元集。)

 

顯然會是亂碼,但是不是所有輸出都是亂碼。

 

Python代碼  收藏代碼
  1. # -*- coding: utf-8 -*-  
  2. # file: example4.py  
  3. import string  
  4.   
  5. # 這個是 str 的字元串  
  6. s = '關關雎鳩'  
  7.   
  8. # 這個是 unicode 的字元串  
  9. u = u'關關雎鳩'  
  10.   
  11. # 輸出 str 字元串, 顯示是亂碼  
  12. print s   # 鍏沖叧闆庨笭  
  13.   
  14. # 輸出 unicode 字元串,顯示正確  
  15. print u  # 關關雎鳩  

 

為什麼是 unicode 而不是 str 的字元顯示是正確的? 首先我們需要瞭解 print。與所有語言一樣,這個 Python 命令實際上是把字元列印到標準輸出流 —— sys.stdout。而 Python 在這裡變了個魔術,它會按照 sys.stdout.encoding 來給 unicode 編碼,而把 str 直接輸出,扔給操作系統去解決。

 

這也是為什麼要設置 linux $LANG 環境變數與 SecureCRT 一致,否則這些字元會被 SecureCRT 再轉換一次,才會交給桌面的 Windows 系統用編碼 CP936 或者說 GBK 來顯示。

 

通常情況,sys.stdout.encoding 的值與 linux $LANG 環境變數保持一致:

 

Python代碼  收藏代碼
  1. # -*- coding: utf-8 -*-  
  2. # file: example5.py  
  3. import sys  
  4.   
  5. # 檢查標準輸出流的編碼  
  6. print sys.stdout.encoding  # 設置 $LANG = zh_CN.GBK,  輸出 GBK  
  7.                            # 設置 $LANG = en_US.UTF-8,輸出 UTF-8  
  8.   
  9. # 這個是 unicode 的字元串  
  10. u = u'關關雎鳩'  
  11.   
  12. # 輸出 unicode 字元串,顯示正確  
  13. print u  # 關關雎鳩  

 

但是,這裡有 陷阱二:一旦你的 Python 代碼是用管道 / 子進程方式運行,sys.stdout.encoding 就會失效,讓你重新遇到 UnicodeEncodeError。

 

比如,用管道方式運行上面的 example4.py 代碼:

 

Python代碼  收藏代碼
  1. python -u example5.py | more  
  2.   
  3. UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)  
  4. None  

 

可以看到,第一:sys.stdout.encoding 的值變成了 None;第二:Python 在 print 時會嘗試用 ascii 去編碼 unicode.

 

由於 ascii 字元集不能用來表示中文字元,這裡當然會編碼失敗。

 

怎麼解決這個問題? 不知道別人是怎麼搞定的,總之我用了一個醜陋的辦法:

 

Python代碼  收藏代碼
  1. # -*- coding: utf-8 -*-  
  2. # file: example6.py  
  3. import os  
  4. import sys  
  5. import codecs  
  6.   
  7. # 無論如何,請用 linux 系統的當前字元集輸出:  
  8. if sys.stdout.encoding is None:  
  9.     enc = os.environ['LANG'].split('.')[1]  
  10.     sys.stdout = codecs.getwriter(enc)(sys.stdout)  # 替換 sys.stdout  
  11.   
  12. # 這個是 unicode 的字元串  
  13. u = u'關關雎鳩'  
  14.   
  15. # 輸出 unicode 字元串,顯示正確  
  16. print u  # 關關雎鳩  

 

這個方法仍然有個副作用:直接輸出中文 str 會失敗,因為 codecs 模塊的 writer 與 sys.stdout 的行為相反,它會把所有的 str 用 sys.getdefaultencoding() 的字元集轉換成 unicode 輸出。

 

Python代碼  收藏代碼
  1. # 這個是 str 的字元串  
  2. s = '關關雎鳩'  
  3.   
  4. # 輸出 str 字元串, 異常  
  5. print s   # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

 

顯然,sys.getdefaultencoding() 的值是 'ascii', 編碼失敗。

 

解決辦法就像 example3.py 里說的,你要麼給 str 加上 u 申明成 unicode,要麼通過“後門”去修改 sys.getdefaultencoding():

 

Python代碼  收藏代碼
  1. # 使得 sys.getdefaultencoding() 的值為 'utf-8'  
  2. reload(sys)                      # reload 才能調用 setdefaultencoding 方法  
  3. sys.setdefaultencoding('utf-8')  # 設置 'utf-8'  
  4.   
  5. # 這個是 str 的字元串  
  6. s = '關關雎鳩'  
  7.   
  8. # 輸出 str 字元串, OK  
  9. print s   # 關關雎鳩  

 

總而言之,在 Python 2 下進行中文輸入輸出是個危機四伏的事,特別是在你的代碼里混合使用 str 與 unicode 時。

 

有些模塊,例如 json,會直接返回 unicode 類型的字元串,讓你的 % 運算需要進行字元解碼而失敗。而有些會直接返回 str, 你需要知道它們的真實編碼,特別是在 print 的時候。

 

為了避免一些陷阱,上文中說過,最好的辦法就是在 Python 代碼里永遠使用 u 定義中文字元串。另外,如果你的代碼需要用管道 / 子進程方式運行,則需要用到 example6.py 里的技巧。

 

(完)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在大家查看HTML時,經常會看到data-role、data-theme等的使用,比如:通過如下代碼即可實現頁眉的效果: 為什麼寫一個data-role="header"就能實現底部為黑色、文字居中顯示的效果呢? 本文提供一種最簡單的實現辦法,讓大家對這些用法有個直觀的瞭解。 我們寫一個html頁面 ...
  • html5中<dialog>標簽作用是定義特殊術語或短語,這裡主機吧詳細介紹下<dialog>標簽用法、<dialog>標簽屬性以及<dialog>標簽應用實例。 <dialog>標簽用法: 用於定義對話框或視窗。 <dialog>標簽屬性: H5 : 表示HTML5 中的新屬性。 <dialog> ...
  • 組件系統是Vue的一個重要組成部分,它可以將一個複雜的頁面抽象分解成許多小型、獨立、可復用的組件,通過組合組件來組成應用程式,結合``vue-router``的路由功能將各個組件映射到相應的路由上,通過路由的變化來告訴Vue要在哪裡渲染他們,實現各個組件、各個頁面之間的跳轉導航。 ...
  • JavaScript作為一個動態語言,很大程度上的詬病就是缺少了面向對象的 類 這個概念,ES5傳統的方法是通過構造函數來實現類的特性;ES6引入了類這一概念,將 這個概念作為對象的模板,通過關鍵字 可以定義類;本質上ES6中引入的類是一個語法糖,其大部分功能ES5均可實現; JavaScript語 ...
  • 一.插值 v-once 通過使用 v-once 指令,你也能執行一次性地插值,當數據改變時,插值處的內容不會更新。但請留心這會影響到該節點上所有的數據綁定: v-html 雙大括弧會將數據解釋為普通文本,而非 HTML 代碼。為了輸出真正的 HTML,你需要使用 v-html 指令: 這個 span ...
  • 說是模塊,其實在MVC中就是區域,新建一個區域專門管理整個微信功能。 Web項目新建區域 在Web項目Areas目錄下新建一個區域,名稱為“Weixin",如下圖: 接著打開web.config,修改如下代碼: 文件路徑:D:\abp version\aspnet-zero-3.4.0\aspnet ...
  • 字元串是Python中最常用的數據類型,而且很多時候你會用到一些不屬於標準ASCII字元集的字元,這時候代碼就很可能拋出UnicodeDecodeError: 'ascii' codec can't decode byte 0xc4 in position 10: ordinal not in ra ...
  • 呵呵!作為一名教python的老師,我發現學生們基本上一開始很難搞定python的裝飾器,也許因為裝飾器確實很難懂。搞定裝飾器需要你瞭解一些函數式編程的概念,當然還有理解在python中定義和調用函數相關語法的一些特點。 我沒法讓裝飾器變得簡單,但是通過一步步的剖析,我也許能夠讓你在理解裝飾器的時候 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...