關於編碼問題的理解(python)

1 字符編碼簡介

1.1 ASCII

ASCII:AmericanStandard Code for Information Interchange。

 計算機是美國人發明的,因此最早只有127個字符被編碼到計算機裏,也就是大小寫英文字母、數字和一些符號,這個編碼表被稱爲ASCII編碼,比如大寫字母A的編碼是65,小寫字母a的編碼是97。

  ASCII碼使用指定的7位或8位二進制數組合來表示128或256種可能的字符。標準ASCII碼也叫基礎ASCII碼,使用7位二進制數(剩下的1位二進制爲0)來表示所有的大寫和小寫字母,數字0到9、標點符號,以及在美式英語中使用的特殊控制字符。

  全世界有上百種語言,日文編到Shift_JIS裏,韓文編到Euc-kr裏,各國有各國的標準,就不可避免地出現衝突,結果就是,在多語言混合的文本中,顯示出來會有亂碼。

1.2 Unicode的編碼和解碼

 Unicode(萬國碼)是計算機科學領域裏的一項業界標準,包括字符集、編碼方案等。Unicode 是爲了解決傳統的字符編碼方案的侷限而產生的,它爲每種語言中的每個字符設定了統一併且唯一的二進制編碼,以滿足跨語言、跨平臺進行文本轉換、處理的要求。

 在文字處理方面,統一碼爲每一個字符而非字形定義唯一的代碼(即一個整數)。換句話說,統一碼以一種抽象的方式(即數字)來處理字符,並將視覺上的演繹工作(例如字體大小、外觀形狀、字體形態、文體等)留給其他軟件來處理,例如網頁瀏覽器或是文字處理器。

 做個解釋:Unicode碼是一個字符集,規定了某個數字對應唯一的字符。 對我們人類來說來說Unicode只是一串數字,誰知道這串數字代表什麼呢?我們知道‘\\u4e2d’unicode碼對應的就是某個未知字符。但是怎樣從數字顯示出漢字呢?不同的編碼使用不同的存儲方式來將對應的字符畫出來,讓我們來看清楚數字對應的原來是中文“漢”。

 

 

import codecs
print (u'漢'.encode('unicode-escape'))
# 得到的結果是\\u6c49
print (ord(u'漢'))
# 得到結果是27721
print(format(27721,'x'))
# 將結果按照十六進制轉化得到結果是6c49
print ("\u6c49")
# 得到的結果是"漢"
print ( "\\u{}".format(format(27721, 'x')))
# 得到的結果是\\u6c49
print (0x6c49)
# 得到結果是27721
a="\\u{}".format(format(27721, 'x'))
print (codecs.decode(a,'unicode_escape'))
# 得到結果是"漢"
print (codecs.decode(a,'unicode_escape'))
# 得到結果是"漢"

 

 Unicode只是一套編碼系統,包含所有字符集,卻並不規定編碼後的二進制代碼如何存儲。

 

 那麼將Unicode字符串轉換爲特定字符編碼(ASCII、UTF-8、GBK)對應的字節串的過程和規則就是編碼。特定字符編碼(ASCII、UTF-8、GBK)的字節串轉換爲對應的Unicode字符串的過程和規則就是解碼

 

1.3utf-8編碼

 

 互聯網的普及,強烈要求出現一種統一的編碼方式。UTF-8 就是在互聯網上使用最廣的一種 Unicode 的實現方式。其他實現方式還包括 UTF-16(字符用兩個字節或四個字節表示)和 UTF-32(字符用四個字節表示),不過在互聯網上基本不用。重複一遍,這裏的關係是,UTF-8 是 Unicode 的實現方式之一。

 UTF-8 最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度。

 UTF-8 的編碼規則很簡單,只有二條:

 1)對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的 Unicode 碼。因此對於英語字母,UTF-8 編碼和 ASCII 碼是相同的。

 2)對於n字節的符號(n > 1),第一個字節的前n位都設爲1,第n + 1位設爲0,後面字節的前兩位一律設爲10。剩下的沒有提及的二進制位,全部爲這個符號的 Unicode 碼。

 

 

 

 

  • UTF有哪些分類?

 

 UTF-8分爲兩種,一種是不帶BOM的,一種是帶BOM的。其中第一種不帶BOM的是標準形式,第二種帶BOM的主要是微軟的習慣。

  • 爲什麼有BOM的UTF-8?

 微軟在UTF-8中使用BOM(Byte order mark)是因爲這樣可以將UTF-8和ASCII等編碼明確區分開。
 windows對於utf-8格式的文件存儲默認是帶有BOM的格式

  • 爲什麼BOM不受歡迎?

 因爲在UNIX環境下,很多的UNIX程序不認識BOM。主要是在UNIX所有腳本語言首行爲#!標示,它依賴於shell解析,而很多shell出於兼容的考慮不檢測BOM,所以加進BOM時shell會把它解釋爲某個普通字符輸入導致破壞#!標示。比如很多現代腳本語言,例如python,其解釋器本身是能處理BOM的,但是shell卡在這裏。
因此我們在linux服務器上讀取這些txt文件時,會遇到如下報錯:
\xef\xbb\xbf…

  • 怎麼解決?

使用codecs庫,將文件轉換爲utf-8-sig格式

 

 

 

1.4 字符與字節

 

 一個字符往往有多種表示方法(字符編碼),根據不同編碼規則得到的字符會佔用不同的字節數。比如字母A-Z都可以用ASCII碼錶示(佔一個字節),也可以用Unicode表示(佔兩個字節),還可以用UTF-8表示(佔一個字節)。

 unicode字符集中一個字符只佔用一個字節;它長的是這個樣子的:u'\u3039'

 我們在python2.7中可以使用以下命令查看unicode字符集以及各種編碼:

>>>u'中'.encode('unicode-escape')

unicode字符是這個樣子的:‘\\u4e2d’

>>>u'中'.encode('gbk').encode('string-escape')    # 將“中”用gbk編碼後輸出編碼值

可以看出GBK編碼字符佔用兩個字節:'\\xd6\\xd0'

>>>u'中'.encode('utf-8').encode('string-escape') # 將“中”用utf-8編碼後輸出編碼值

可以看出utf-8編碼字符佔用了三個字節:‘\\xe4\\xb8\\xad’

 

import codecs
import random
def random_char():
    char_code = random.randint(0x4e00, 0x9fbb)    # 以0x開始表示16進制。
    str = "\\u{}".format(format(char_code, 'x'))
    print (str)
    char = codecs.decode(str,'unicode_escape')  # 翻譯計算機看到的二進制編碼變成我們能看到的unicode
    g_char=codecs.encode(char,'gbk')
    u_char=codecs.encode(char,'utf-8')
    return char,g_char,u_char
for x in range(100):
    print(random_char())

 

 

 

 

2 編碼問題的產生

2.1python3編碼

 

在Pyhon3中字符編碼有了很大改善最主要的有以下幾點:

1.Python 3的源碼.py文件 的默認編碼方式爲UTF-8,所以在Python3中你可以不用在py腳本中寫coding聲明,並且系統傳遞給python的字符不再受系統默認編碼的影響,統一爲unicode編碼。

2.將字符串和字節序列做了區別,字符串str是字符串標準形式與2.x中unicode類似,bytes類似2.x中的str有各種編碼區別。bytes通過解碼轉化成str,str通過編碼轉化成bytes。

還有一點。在python3中,我們將不能直接看到unicode字節串,'\\u5c0f\\u660e'會被顯示爲中文的“小明”;因爲python3默認使用unicode編碼,unicode字節串將被直接處理爲中文顯示出來。

2.2 默認編碼

 Unicode纔是真正的字符串,而用ASCII、UTF-8、GBK等字符編碼表示的是字節串。從上面對各種編碼方式的介紹中,我們也可以瞭解到,像ASCII、UTF-8、GBK這些都是編碼方式,是將字符編碼爲字節碼。Unicode只是一個符號集,它只規定了符號的二進制代碼,也就是說它給每一個字符一個獨一無二的數字來表示。

 下面我用python爲例子來說明編碼和解碼的過程:

 我們都知道,磁盤上的文件都是以二進制格式存放的,其中文本文件都是以某種特定編碼的字節形式存放的。對於程序源代碼文件的字符編碼是由編輯器指定的,比如我們使用Pycharm來編寫Python程序時會指定工程編碼和文件編碼爲UTF-8,那麼Python代碼被保存到磁盤時就會被轉換爲UTF-8編碼對應的字節(encode過程)後寫入磁盤。

 當執行Python代碼文件中的代碼時,Python解釋器在讀取Python代碼文件中的字節串之後,需要將其轉換爲Unicode字符串(decode過程)之後才執行後續操作。

   如果我們沒有在代碼文件指定字符編碼,Python解釋器會使用哪種字符編碼把從代碼文件中讀取到的字節轉換爲Unicode字符串呢?就像我們配置某些軟件時,有很多默認選項一樣,需要在Python解釋器內部設置默認的字符編碼來解決這個問題,這就是“默認編碼”。python2中默認編碼是ascii;  python3中默認的編碼是utf-8。

 

 

 從這裏也可以看出。不同的編碼也就是不同的儲存方式。當一個經過編碼成utf-8的“中”一般佔用三個字節。不同的系統交互的時候也就是你從系統A將三個字節的“中”按照utf-8的方式解碼成unicode碼,然後根據unicode碼再編碼成GBK方式的編碼在系統B中正常顯示。但是當解碼和編碼搞錯的時候。你用解碼二個字節gbk的解碼方式去解碼一個三個字節的“中”一定是解碼錯誤。而很明顯一個不是unicode碼是不可以進行編碼的!

2.3常見編碼問題

常見出現編碼問題的場景:

a.不熟悉代碼編輯器的編碼或者是在於不同系統交互時產生的解碼錯誤;

 例如在Windows中的命令行下使用的Python Shell,其編碼格式是GBK,當你在shell中鍵入string1 = u'我是李尋歡'時,會發生如下事件:

 1, Python獲取當前命令行編碼(sys.stdin.encoding),爲cp936(即GBK)

 2, 將輸入的漢字我是李尋歡按GBK編碼轉碼成Unicode的形式,並據此創建Unicode類型的字符串,賦值給string1當需要打印輸出時,Python會首先調取字符輸出程序(命令行或者輸出函數)的編碼格式,然後將該字符串編碼成字符輸出程序所用的編碼(這樣字符輸出程序就不會因爲認不出編碼而出現亂碼),接着字符輸出程序將編碼後的字符輸出到目的地。該處理過程是字符輸入程序(上一條)的逆過程。

b.用編輯器讀取文件(需要特別注意的是window中帶bom和不帶bom文件);

下面我用一個csv文件來示例:

 

# 判斷文件的編碼類型

import chardet
with open('d:\\school\\student_2015_5_8_costs_utf8.csv','rb') as f:
    data=f.readline()
    print (chardet.detect(data))
#{'encoding': 'GB2312', 'confidence': 0.99, 'language': 'Chinese'}
# b'id,daytime,\xc6\xe4\xcb\xfb,\xcd\xbc\xca\xe9\xb9\xdd,\xbf\xaa\xcb\xae,\xbd\xcc\xce\xf1\xb4\xa6,\xce\xc4\xd3\xa1\xd6\xd0\xd0\xc4,\xd0\xa3\xd2\xbd\xd4\xba,\xd0\xa3\xb3\xb5,\xcf\xb4\xd2\xc2\xb7\xbf,\xc1\xdc\xd4\xa1,\xb3\xac\xca\xd0,\xca\xb3\xcc\xc3,All\r\n'

# 下面介紹的是如果一個gbk編碼的csv文件,用utf-8解碼出現亂碼的時候。

import codecs
import pandas as pd

def GBK_2_UTF8(src,dst):
    with codecs.open(src,"r",encoding='gbk') as f:   # 按照指定的編碼打開文件
        g_con=f.read()
    with codecs.open(dst,"w",encoding='utf_8') as wf:   # 按照指定的編碼打開文件
        wf.write(g_con)

GBK_2_UTF8('d:\\school\\student_2015_5_8_costs.csv','d:\\school\\student_2015_5_8_costs_utf8.csv')
aa=pd.read_csv('d:\\school\\student_2015_5_8_costs_utf8.csv')
# 打開的數據無法正常顯示。部分顯示是亂碼但是python讀取沒有問題

bb=pd.read_csv('d:\\school\\student_2015_5_8_costs.csv',encoding='gbk')
cc=bb.to_csv('d:\\school\\student_2015_5_8_costs_pdutf-8.csv',encoding='utf-8')
# pd.read_csv以gbk打開再以utf-8另存到文件。但是仍然存在上面的問題,顯示亂碼但是python讀取沒有問題

cc=bb.to_csv('d:\\school\\student_2015_5_8_costs_pdutf-8.csv',encoding='utf_8_sig') 
# 以UTF_8 with BOM編碼(微軟產品能正確識別UTF_8 with BOM存儲的中文文件)存儲 


cc=bb.to_csv('d:\\school\\student_2015_5_8_costs_pdutf-8.csv',encoding='utf_8_sig') 
# 以UTF_8 with BOM編碼(微軟產品能正確識別UTF_8 with BOM存儲的中文文件)存儲 


 

 

文件有utf-8 bom和utf-8無bom格式:

python在讀取文件首行數據時,如果是utf-8 bom格式的文件,則首行讀取的是有bom信息的,

和utf-8 無bom格式的文件是不同的。

 

#如何分辨utf-8文件是否含有bom
# 環境py3.6
import codecs
with open('d:\\school\\student_2015_5_8_costs.csv') as h:
    content = h.read()
    bom=str(codecs.BOM_UTF8)
    if content.startswith(bom):
        content = content.decode('utf_8_sig')
        print('帶有bom的文件')
    else:
        content
        print ('不帶有bom的文件')
    # print (content)


以上是個人淺見。歡迎提出意見。如有意見,請聯繫我的郵件[email protected]

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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