一篇文章詳解python的字符編碼問題


一:什么是編碼

  將明文轉換為計算機可以識別的編碼文本稱為“編碼”。反之從計算機可識別的編碼文本轉回為明文為“解碼”。

  那么什么是明文呢,首先我們從一段信息說起,消息以人們可以理解,易懂的表示存在,我們把這個表示為明文(plain text)。對於說英文的人,紙張上打印的或者屏幕上顯示的英文都算是明文。

二:都有什么編碼格式?

1:ASCII(占一個字符,只支持英文)

  計算機上的數據都是以二進制的形式存儲的,1個字節(8比特)可以表示256種狀態,英文只有26個字符,再加上一些特殊的字符,使用128個就夠了,計算機就可以使用127個不同字節來表示英文文字,這就是ASCII碼

2:GB2312(占兩個字符,支持6700+漢字)

  計算機進入中國后,無法顯示中文,一個字節已經被占滿了,我國重新制定了一個編碼表,將擴展的第八位對應的拉丁文全部刪掉,規定一個小於127的字符與原來的意義相同,當兩個大於127的字符連接在一起的時候,就表示一個漢字,前面一個字節為高字節,后面一個字節為低字節,這樣就可以表示7000多漢字,這種編碼叫做GB2312。GB2312是對ASCII的中文擴展

3:GBK和GB18030(GB2312的升級版,支持21000+漢字)

  由於漢字的數量太大,GB2312是不能滿足需求,后面規定只要第一個字節大於127就固定表示一個漢字,不管后面的是不是擴展字符里面的內容,擴展后的編碼稱為GBK,GBK包括了GB2312的所有內容,同時增加了近20000個新的漢字和符號

4:Shift-JIS 日本編碼(這里不過多解釋)

5:TIS-620泰國編碼(這里不過多解釋)

6:ks_c_5601-1987韓國編碼(這里不過多解釋)

  由於每個國家都有自己的字符,所以其對應關系也涵蓋了自己國家的字符,但是以上編碼都存在局限性,即:僅涵蓋本國字符,無其他國家字符的對應關系。應運而生出現了萬國碼,他涵蓋了全球所有的文字和二進制的對應關系

7:Unicode(2-4字節,已經收錄了136690個字符,並一直擴展)

  在uincode出現之前,每隔國家都搞自己的編碼,彼此之間互不支持,帶來了許多不方便,國際標准組織提出來一個統一的編碼標准:unicode

  unicode用兩個字符來表示一個字符,可以提供65535種字符,足夠覆蓋世界上所有的字符

8:UTF-8(Unicode Transformation Format)

  unicode的出現,提供了統一的標准,但對於英文世界來說,一個字節完全夠用,如果使用unicode會浪費大量的空間,為了解決這個問題提出來utf-8,一種針對unicode的可變長度字符串,可以使用1-4個字符表示一個符號,根據不同的符號變化字節長度,當字符在ASCII編碼范圍內,用一個字節表示,兼用ASCII。

  使用這樣編碼的好處是,雖然內存匯總的數據都是unicode,但是當數據保存到磁盤或者用於網絡傳輸時,使用utf-8會節省更多的流量和硬盤空間。

  • UTF-8: 使用1、2、3、4個字節表示所有字符;優先使用1個字符、無法滿足則使增加一個字節,最多4個字節。英文占1個字節、歐洲語系占2個、東亞占3個,其它及特殊字符占4個
  • UTF-16: 使用2、4個字節表示所有字符;優先使用2個字節,否則使用4個字節表示。
  • UTF-32: 使用4個字節表示所有字符;

  unicode和utf-8的關系:unicode是內存編碼表示方案,而utf-8是如何保存和傳輸unicode的方案

三:編碼的轉換

  雖然國際語言是英語 ,但大家在自己的國家依然說自已的語言,不過出了國, 你就得會英語
編碼也一樣,雖然有了unicode and utf-8 , 但是由於歷史問題,各個國家依然在大量使用自己的編碼,比如中國的windows,默認編碼依然是gbk,而不是utf-8

  基於此,如果中國的軟件出口到美國,在美國人的電腦上就會顯示亂碼,因為他們沒有gbk編碼。
  若想讓中國的軟件可以正常的在 美國人的電腦上顯示,只有以下2條路可走:

  1. 讓美國人的電腦上都裝上gbk編碼
  2. 把你的軟件編碼以utf-8編碼

  第1種方法幾乎不可能實現,第2種方法比較簡單。 但是也只能是針對新開發的軟件。 如果你之前開發的軟件就是以gbk編碼的,上百萬行代碼可能已經寫出去了,重新編碼成utf-8格式也會費很大力氣。

  so , 針對已經用gbk開發完畢的項目,以上2種方案都不能輕松的讓項目在美國人電腦上正常顯示,難道沒有別的辦法了么?
  有, 還記得我們、、講unicode其中一個功能是其包含了跟全球所有國家編碼的映射關系,意思就是,你寫的是gbk的“學習”,但是unicode能自動知道它在unicode中的“學習”的編碼是什么,如果這樣的話,那是不是意味着,無論你以什么編碼存儲的數據 ,只要你的軟件在把數據從硬盤讀到內存里,轉成unicode來顯示,就可以了。
由於所有的系統、編程語言都默認支持unicode,那你的gbk軟件放到美國電腦 上,加載到內存里,變成了unicode,中文就可以正常展示啦。

unicode與gbk的映射表 http://www.unicode.org/charts/ 

  

四:python2.x的編碼

  在python2.x中,有兩種字符串類型:str類型和unicode類型。這兩個類型只是python定義的兩個名字,關鍵還要看這兩種數據類型在內存中的存儲方法是什么

str和unicode都是basestring的子類。嚴格意義上說,str其實是字節串,它是unicode經過編碼后的字節組成的序列。

  而unicode是一個字符串,str是unicode這個字符串經過編碼(utf8,gbk等)后的字節組成的序列。

unicode才是真正意義上的字符串,對字節串str使用正確的字符編碼進行解碼后獲得

在Py2里,str=bytes。python2的字符串其實更應該稱為字節串

py2編碼的最大特點是Python 2 將會自動的將bytes數據解碼成 unicode 字符串

所以在2里我們可以將字節與字符串拼接。

Python 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:25:58) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> s = '學習'
>>> print type(s)
<type 'str'>
>>> print repr(s)
'\xd1\xa7\xcf\xb0'

>>> s = u'學習'
>>> print type(s)
<type 'unicode'>
>>> print repr(s)
u'\u5b66\u4e60'

  由上面的例子可以看出str和unicode分別存儲的是字節數據和unicode數據

  但是,python2.x悄悄掩蓋掉了byte到unicode的轉換,只要數據全部是ASCII的話,所有的轉換都是正確的,一個一個非ASCII的字符進入你的程序,那么默認的解碼將會失效,從而造成unicodedecodeerror的錯誤,py2編碼讓程序在出路ASCII的時候非常簡單,付出的代價就是在處理非ASCII的時候將會失敗。

由於Python創始人在開發初期認知的局限性,其並未預料到python能發展成一個全球流行的語言,導致其開發初期並沒有把支持全球各國語言當做重要的事情來做,所以就輕佻的把ASCII當做了默認編碼。 當后來大家對支持漢字、日文、法語等語言的呼聲越來越高時,Python於是准備引入unicode,但若直接把默認編碼改成unicode的話是不現實的, 因為很多軟件就是基於之前的默認編碼ASCII開發的,編碼一換,那些軟件的編碼就都亂了。所以Python 2 就直接 搞了一個新的字符類型,就叫unicode類型,比如你想讓你的中文在全球所有電腦上正常顯示,在內存里就得把字符串存成unicode類型

五:python3.x的編碼

  python3.x也有兩種數據類型,str和bytes;str類型存unicode數據,bytes類型存bytes數據,

  python3.x將utf-8或者gbk等編碼的字節數據轉化為python3.x的str類型,utf-8編碼的bytes<-->str

  python2.x將utf-8或者gbk等編碼的字節數據轉化為python2.x的unicode類型,utf-8編碼的str<-->unicode

  python3.x的編碼思想就是它清晰的將文本和二進制區分開了,不會對bytes字節進行自動編碼。文本總是unicode,由str類型表示,二進制數據則由bytes類型表示,Python 3不會以任意隱式的方式混用str和bytes,將兩者明確地區分開。基於此,Python3中不能拼接字符串和字節包,也不可以在字節包里搜索字符串(反之亦然),也不能向使用字符串參數的函數中傳入字節包參數(反之亦然)。

python3 執行代碼的過程

  1. 解釋器找到代碼文件,把代碼字符串按文件頭定義的編碼加載到內存,轉成unicode
  2. 把代碼字符串按照語法規則進行解釋,
  3. 所有的變量字符都會以unicode編碼聲明
######在python2.x#######
print(b'hello'+'world')
會輸出
helloworld

######在python3.x#######
print(b'hello'+'world')
會輸出
TypeError: can't concat str to bytes

  本來這樣就可以結束了,但是上面的utf-8編碼之所以能在windows gbk的終端下顯示正常,是因為到了內存里python解釋器把utf-8轉成了unicode , 但是這只是python3, 並不是所有的編程語言在內存里默認編碼都是unicode,比如 萬惡的python2 就不是, 它的默認編碼是ASCII,想寫中文,就必須聲明文件頭的coding為gbk or utf-8, 聲明之后,python2解釋器僅以文件頭聲明的編碼去解釋你的代碼,加載到內存后,並不會主動幫你轉為unicode,也就是說,你的文件編碼是utf-8,加載到內存里,你的變量字符串就也是utf-8, 這意味着什么你知道么?。。。意味着,你以utf-8編碼的文件,在windows是亂碼。

亂是正常的,不亂才不正常,因為只有2種情況 ,你的windows上顯示才不會亂

  1. 字符串以GBK格式顯示
  2. 字符串是unicode編碼

  既然Python2並不會自動的把文件編碼轉為unicode存在內存里, 那就只能使出最后一招了,你自己人肉轉。Py3 自動把文件編碼轉為unicode必定是調用了什么方法,這個方法就是,decode(解碼) 和encode(編碼)

  時間來到2008年,python發展已近20年,創始人龜叔越來越覺得python里的好多東西已發展的不像他的初衷那樣,開始變得臃腫、不簡潔、且有些設計讓人摸不到頭腦,比如unicode 與str類型,str 與bytes類型的關系,這給很多python程序員造成了困擾。
  龜叔再也忍不了,像之前一樣的修修補補已不能讓Python變的更好,於是來了個大變革,Python3橫空出世,不兼容python2,python3比python2做了非常多的改進,其中一個就是終於把字符串變成了unicode,文件默認編碼變成了utf-8,這意味着,只要用python3,無論你的程序是以哪種編碼開發的,都可以在全球各國電腦上正常顯示,真是太棒啦!

  PY3 除了把字符串的編碼改成了unicode, 還把str 和bytes 做了明確區分, str 就是unicode格式的字符, bytes就是單純二進制啦。

六:文件存儲讀取過程中的編碼問題

  對於文本編輯器word等軟件,當我們在這些軟件上編輯文字的時候,無論是什么語言的文字或符號,計算機都是無法識別的。
那么在保存之前數據是通過什么形式存在內存的呢?
是unicode數據,為什么要存unicode數據,這是因為無論世界上的任何字符它都有唯一編碼對應,兼容性是最好的。
當我們保存了存到磁盤上的數據又是什么呢?
是通過某種編碼方式編碼的bytes字節串。比如utf8---一種可變長編碼,很好的節省了空間;還可以是gbk等編碼方式。
在我們的文本編輯器軟件都有默認的保存文件的編碼方式,比如utf-8,gbk等。當我們保存的時候,這些編輯軟件已經"默默地"做了編碼工作。
那當我們再打開這個文件時,軟件又默默地給我們做了解碼的工作,將數據再解碼成unicode,然后就可以呈現明文給用戶了!
所以,unicode是離用戶更近的數據,bytes是離計算機更近的數據。

七:編碼與程序運行的關系

編寫python代碼一般會用到pycharm ,sublime等軟件,而代碼文件的創建,保存,執行等過程就伴隨着編碼解碼流程,使用pycharm創建的hello.py文件,當我們保存的時候,文件就以pycharm默認的編碼方式保存到磁盤,關閉文件再打開,pycharm就會以默認的編碼方式對該文件打開后讀到的內容就行解碼,轉成unicode到內存我們就看到了我們的明文;

而如果點開運行按鈕或者在命令行運行該文件時候,python解釋器就會被調用,打開文件,然后將存儲在磁盤上的bytes數據解碼成unicode數據,這個過程和編譯器是一樣的,不同的是解釋器將會把這些unicode數據翻譯成c代碼再轉成二進制的數據流,最后通過控制操作系統調用cpu來執行這些二進制數據,整個過程才算結束,

python2.x默認的是ASCII碼,python3.x默認的是utf-8,可以通過下面的方式查詢:

import sys 
print(sys.getdefaultencoding())

八,字符編碼轉換總結

python2.x

    內存中字符默認編碼是ASCII,默認文件編碼也是ASCII

    當聲明了文件頭的編碼后,字符串的編碼就按照文件編碼來,總之,文件編碼是什么,那么python2.x的str就是什么

    python2.x的unicode是一個單獨的類型,按u"編碼"來表示

    python2.x  str==bytes,bytes直接是按照字符編碼存成2進制格式在內存里

python3.x

    字符串都是unicode

    文件編碼都默認是utf-8,讀到內存會被python解釋器自動轉成unicode

    bytes和str做了明確的區分

    所有的unicode字符編碼后都會編程bytes格式

九:print語句和print函數的區別

print語句

在python2.x中,print語句最簡單的使用形式是

print hello world!

這相當於執行了

sys.stdout.write(‘’。join(map(str,[hello world!]))+'\n')

如果以逗號為分隔符,傳遞額外的參數,這些參數會被傳遞到str()函數,最終打印的時候,每個參數之間會空一行。

從2.0版本開始,python引入了print>>的語法。作用是重定向print語句最終輸出的字符串的文件

例如:

print>>output

相當於

output.write(str(hello)+'\n')

print函數

如果用python來實現print的函數,他的函數定義應該是這樣的

import sys

def print(*objects, sep=None, end=None, file=None, flush=False):
    """A Python translation of the C code for builtins.print().


"""
    if sep is None:
        sep = ' '
    if end is None:
        end = '\n'
    if file is None:
        file = sys.stdout
    file.write(sep.join(map(str, objects)) + end)
    if flush:
        file.flush()

函數定義

  從上面的代碼我們可以發現,python3.x的print 函數實現了print語句的所有特性。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM