字元編碼(四)|python2中的編碼問題

這是編碼系列最後一篇,只講有關python2的部分。

python2與python3相比有些設定引起了很多莫名其妙的編碼問題,我們通常詬病python2在編碼方面的坑其實指的就是這部分莫名其妙的問題。

我們通常說的正常的編碼問題是上文提到的python3中會遇到的編碼問題

  • 文件或網頁的編碼方式和讀取時使用的方式不同,這是非常正常的,解決思路也非常清晰,只要使用正確的編碼就可以了
  • encodedecode方法編碼和解碼時,使用錯誤編碼造成的錯誤,這也是找到正確的編碼就可以解決

而說python2中的很多編碼問題莫名其妙,是因為這些編碼解碼不是人為指定的,而是經常在你不知道的時候,程序就按照某種編碼方式對某些字元進行了編碼或解碼,而它使用的這種編碼方式還不正確,導致了報錯或亂碼。此時使用者會覺得自己根本沒有做編碼或解碼的工作怎麼就會發生編碼方面的錯誤。下面就來大致講一講這些問題出現的原因,相信看完上面的基礎,理解python2中的問題是非常容易的。

這些莫名其妙的編碼問題主要是由兩個默認編碼造成的

  • py2中默認編碼方式是ASCII,py3是UTF-8(這個默認編碼在什麼時候會遇到後面會講),這個是引起編碼問題的主要原因
  • py2中用a=中文定義的字元串其實不是真正的字元串,它不是Unicode,所以說在定義a時默認進行了一次編碼轉換,這個編碼轉換和上面的ASCII還沒有關係,使用的是另一種默認的編碼

上那兩個默認編碼在python3中幾乎不會用到,它們產生的原因在於pyhton2設計上的缺陷,下面就列一下python2是如何導致的編碼錯誤,一共5種錯誤。

1.定義str時的自動編碼

在python2中,a=中文這個a不是Unicode,b=u中文這個b才是。而在python3中根本沒有u中文這一說,a=中文這個a就是Unicode。

準確地說,a叫位元組串,b才叫字元串(前面提到過Unicode和字元串完全等同)。a其實是一個二進位位元組流,是字元串encode後的產物。所以說在a=中文這個賦值過程中,默認進行了一次編碼encode,將』中文』這個字元串編碼成了二進位數。

既然是編碼,肯定是按照某一套編碼方式來做的。這個編碼方式再不同場景是不一樣的。用下面代碼查看當前默認編碼是什麼

import localelocale.getdefaultlocale()

這就是兩種默認編碼中的第二種。

一般在windows命令行下結果是(zh_CN, cp936),即使設置了chcp 65001,或者在jupyter中使用也是一樣是cp936。

說明定義字元串(準確來說應該叫位元組串)時,在windows下都會默認用GBK進行編碼。

字元串的兩種定義方式會讓使用者產生混亂,更容易觸犯到後面的那些雷區。

解碼時如果不能理解它原來是怎麼編碼的就會使用錯誤的編碼從而造成報錯。

2.str和unicode混用造成的錯誤

由於第一條中的差異,a=中文這樣定義的a在編碼和解碼方面也與py3是有不同的。

  • 在python3中,這樣定義的a只能encode,因為它是Unicode
  • 在python2中,這個a主要是要decode,因為它是編碼過的

從這一點中,第一種默認編碼上場了。

如果str和unicode混用,比如中文+u好,這個過程會默認將str解碼成unicode,使用的是py2默認的ASCII編碼,而中文不對應ASCII編碼,於是報錯。這是第一種默認編碼產生錯誤的第一種,之後還有很多地方都會有類似的默認轉換。

在這裡首先說一下如何查看這個默認編碼是什麼

import syssys.getdefaultencoding()

就是這個命令,在py2中結果是ascii,在py3中就是utf-8。涉及到的兩個默認編碼都出現了,查看命令也不一樣,注意區分。

另外再說一下關於這點python3是怎麼設計的。如果str和bytes直接相加,會直接報出一個錯誤TypeError: Cant convert bytes object to str implicitly,python3說的很明確不能隱式轉,而不是像python2一樣偷偷給你轉了。

3.py2編碼解碼通用不規範造成的錯誤

這裡要列兩點py2和py3的差別

  • py3中的兩個type:str和bytes分別對應py2中的unicode和str。python2中,a=中文這個a就是str類,b=u中文這個b就是unicode類
  • 在py2中,無論unicode類還是str類,都可以使用encode和decode方法,而py3中明確str只能使用encode,bytes只能使用decode

在py2中,按理說str應該只能decode,而unicode應該只能encode,而現在開放了全都可以,讓使用者更搞不清楚到底應該decode還是encode,如果用錯了會出現如下錯誤

UnicodeEncodeError: ascii codec cant encode characters in position 0-1: ordinal not in range(128)UnicodeDecodeError: ascii codec cant decode byte 0xe4 in position 0: ordinal not in range(128)

其中第一條是將unicode強行decode的報錯,第二條是將str強行encode的報錯。對於這兩種強行解碼編碼的內部運行機制是

  • unicode強行decode時,因為unicode確實不能decode,於是py2會默認將unicode用ascii encode一下,再用得到的結果去decode,就在encode的過程中就報錯了
  • str強行decode時,因為str確實不能encode,於是py2會默認將str用ascii decode一下,再用得到的結果去encode,就在decode的過程中就報錯了

這是第一種默認編碼產生錯誤的第二種情況

4.print時的默認編碼

我們之前提到過,a=中文這樣定義的a其實本身是位元組串而不是字元串,在控制台中輸入a的輸出結果和輸入print(a)的輸出結果是不一樣的

  • 輸入a的輸出結果是16進位形式的,也就是它的本來面貌
  • 輸入print(a)的輸出結果是我們能看懂的字元』中文』

說明在print時其實進行了一次默認解碼,將位元組轉換成了unicode呈現出來。這時print的是str類型,如果傳入的是unicode類型,又會自動按照ascii編碼將unicode encode為str,然後再print出來,如下所示

b = u中文print(b)# UnicodeEncodeError: ascii codec cant encode characters in position 0-1: ordinal not in range(128)

這時第一種默認編碼產生錯誤的第三種情況。

5.函數參數類型傳入錯誤

函數參數本應傳入str類型,而傳入的是unicode類型的時候,也會自動使用ascii編碼encode

這個其實可以算是第4條print的推廣,strraw_input函數都是這樣

b = u中文print(b)str(b)a = raw_input(b)

後三行分別指定都會報錯,也是encode用ascii編碼的問題,這時第一種默認編碼產生錯誤的更多情況。

python2中的編碼問題參考三篇非常好的文章

  • Python 編碼錯誤的本質原因
  • Python 2.x 字元編碼終極指南
  • 熟悉又陌生的字元編碼

因為我的電腦里沒有裝python2,一切測試結果都在這個網站中進行

專欄信息

專欄主頁:Data Analysis

專欄目錄:目錄

版本說明:軟體及包版本說明


推薦閱讀:

TAG:Python | 字元編碼 | 亂碼 |