Python中的字符串與字符編碼:編碼和轉換問題

本節(jié)內容:

前言

相關概念

Python中的默認編碼

Python2與Python3中對字符串的支持

字符編碼轉換

一、前言

Python中的字符編碼是個老生常談的話題沦偎,同行們都寫過很多這方面的文章左刽。有的人云亦云啤誊,也有的寫得很深入。近日看到某知名培訓機構的教學視頻中再次談及此問題狂票,講解的還是不盡人意候齿,所以才想寫這篇文字。一方面闺属,梳理一下相關知識慌盯,另一方面,希望給其他人些許幫助掂器。

Python2的默認編碼是ASCII亚皂,不能識別中文字符,需要顯式指定字符編碼国瓮;Python3的默認編碼為Unicode灭必,可以識別中文字符。

相信大家在很多文章中都看到過類似上面這樣“對Python中中文處理”的解釋乃摹,也相信大家在最初看到這樣的解釋的時候確實覺得明白了禁漓。可是時間久了之后孵睬,再重復遇到相關問題就會覺得貌似理解的又不是那么清楚了播歼。如果我們了解上面說的默認編碼的作用是什么,我們就會更清晰的明白那句話的含義肪康。

需要說明的是荚恶,“字符編碼是什么”撩穿,以及“字符編碼的發(fā)展過程” 不是本節(jié)討論的話題磷支,這些內容可以參考我之前的<<這篇文章>>谒撼。

二、相關概念

1. 字符與字節(jié)

一個字符不等價于一個字節(jié)雾狈,字符是人類能夠識別的符號廓潜,而這些符號要保存到計算的存儲中就需要用計算機能夠識別的字節(jié)來表示。一個字符往往有多種表示方法善榛,不同的表示方法會使用不同的字節(jié)數(shù)辩蛋。這里所說的不同的表示方法就是指字符編碼,比如字母A-Z都可以用ASCII碼表示(占用一個字節(jié))移盆,也可以用UNICODE表示(占兩個字節(jié))悼院,還可以用UTF-8表示(占用一個字節(jié))。字符編碼的作用就是將人類可識別的字符轉換為機器可識別的字節(jié)碼咒循,以及反向過程据途。

UNICDOE才是真正的字符串,而用ASCII叙甸、UTF-8颖医、GBK等字符編碼表示的是字節(jié)串。關于這點裆蒸,我們可以在Python的官方文檔中經(jīng)橙巯簦可以看到這樣的描述"Unicode string" , " translating a Unicode string into a sequence of bytes"

我們寫代碼是寫在文件中的,而字符是以字節(jié)形式保存在文件中的僚祷,因此當我們在文件中定義個字符串時被當做字節(jié)串也是可以理解的佛致。但是,我們需要的是字符串辙谜,而不是字節(jié)串俺榆。一個優(yōu)秀的編程語言,應該嚴格區(qū)分兩者的關系并提供巧妙的完美的支持筷弦。JAVA語言就很好肋演,以至于了解Python和PHP之前我從來沒有考慮過這些不應該由程序員來處理的問題。遺憾的是烂琴,很多編程語言試圖混淆“字符串”和“字節(jié)串”爹殊,他們把字節(jié)串當做字符串來使用,PHP和Python2都屬于這種編程語言奸绷。最能說明這個問題的操作就是取一個包含中文字符的字符串的長度:

對字符串取長度梗夸,結果應該是所有字符串的個數(shù),無論中文還是英文

對字符串對應的字節(jié)串取長度号醉,就跟編碼(encode)過程使用的字符編碼有關了(比如:UTF-8編碼反症,一個中文字符需要用3個字節(jié)來表示辛块;GBK編碼,一個中文字符需要2個字節(jié)來表示)

注意:Windows的cmd終端字符編碼默認為GBK铅碍,因此在cmd輸入的中文字符需要用兩個字節(jié)表示

>>> # Python2

>>> a ='Hello,中國'# 字節(jié)串润绵,長度為字節(jié)個數(shù) = len('Hello,')+len('中國') = 6+2*2 = 10

>>> b =u'Hello,中國'# 字符串,長度為字符個數(shù) = len('Hello,')+len('中國') = 6+2 = 8

>>> c = unicode(a,'gbk')# 其實b的定義方式是c定義方式的簡寫胞谈,都是將一個GBK編碼的字節(jié)串解碼(decode)為一個Uniocde字符串

>>>

>>> print(type(a), len(a))

(,10)

>>> print(type(b), len(b))

(,8)

>>> print(type(c), len(c))

(,8)

>>>

Python3中對字符串的支持做了很大的改動尘盼,具體內容會在下面介紹。

2. 編碼與解碼

先做下科普:UNICODE字符編碼烦绳,也是一張字符與數(shù)字的映射卿捎,但是這里的數(shù)字被稱為代碼點(code point), 實際上就是十六進制的數(shù)字。

Python官方文檔中對Unicode字符串径密、字節(jié)串與編碼之間的關系有這樣一段描述:

Unicode字符串是一個代碼點(code point)序列午阵,代碼點取值范圍為0到0x10FFFF(對應的十進制為1114111)。這個代碼點序列在存儲(包括內存和物理磁盤)中需要被表示為一組字節(jié)(0到255之間的值)享扔,而將Unicode字符串轉換為字節(jié)序列的規(guī)則稱為編碼底桂。

這里說的編碼不是指字符編碼,而是指編碼的過程以及這個過程中所使用到的Unicode字符的代碼點與字節(jié)的映射規(guī)則伪很。這個映射不必是簡單的一對一映射戚啥,因此編碼過程也不必處理每個可能的Unicode字符,例如:

將Unicode字符串轉換為ASCII編碼的規(guī)則很簡單--對于每個代碼點:

如果代碼點數(shù)值<128锉试,則每個字節(jié)與代碼點的值相同

如果代碼點數(shù)值>=128猫十,則Unicode字符串無法在此編碼中進行表示(這種情況下,Python會引發(fā)一個UnicodeEncodeError異常)

將Unicode字符串轉換為UTF-8編碼使用以下規(guī)則:

如果代碼點數(shù)值<128呆盖,則由相應的字節(jié)值表示(與Unicode轉ASCII字節(jié)一樣)

如果代碼點數(shù)值>=128拖云,則將其轉換為一個2個字節(jié),3個字節(jié)或4個字節(jié)的序列应又,該序列中的每個字節(jié)都在128到255之間宙项。

簡單總結:

編碼(encode):將Unicode字符串(中的代碼點)轉換特定字符編碼對應的字節(jié)串的過程和規(guī)則

解碼(decode):將特定字符編碼的字節(jié)串轉換為對應的Unicode字符串(中的代碼點)的過程和規(guī)則

可見,無論是編碼還是解碼株扛,都需要一個重要因素尤筐,就是特定的字符編碼。因為一個字符用不同的字符編碼進行編碼后的字節(jié)值以及字節(jié)個數(shù)大部分情況下是不同的洞就,反之亦然盆繁。

三、Python中的默認編碼

1. Python源代碼文件的執(zhí)行過程

我們都知道旬蟋,磁盤上的文件都是以二進制格式存放的油昂,其中文本文件都是以某種特定編碼的字節(jié)形式存放的。對于程序源代碼文件的字符編碼是由編輯器指定的,比如我們使用Pycharm來編寫Python程序時會指定工程編碼和文件編碼為UTF-8冕碟,那么Python代碼被保存到磁盤時就會被轉換為UTF-8編碼對應的字節(jié)(encode過程)后寫入磁盤拦惋。當執(zhí)行Python代碼文件中的代碼時,Python解釋器在讀取Python代碼文件中的字節(jié)串之后安寺,需要將其轉換為UNICODE字符串(decode過程)之后才執(zhí)行后續(xù)操作厕妖。

上面已經(jīng)解釋過,這個轉換過程(decode我衬,解碼)需要我們指定文件中保存的字節(jié)使用的字符編碼是什么叹放,才能知道這些字節(jié)在UNICODE這張萬國碼和統(tǒng)一碼中找到其對應的代碼點是什么饰恕。這里指定字符編碼的方式大家都很熟悉挠羔,如下所示:

# -*- coding:utf-8-*-

2. 默認編碼

那么,如果我們沒有在代碼文件開始的部分指定字符編碼埋嵌,Python解釋器就會使用哪種字符編碼把從代碼文件中讀取到的字節(jié)轉換為UNICODE代碼點呢破加?就像我們配置某些軟件時,有很多默認選項一樣雹嗦,需要在Python解釋器內部設置默認的字符編碼來解決這個問題范舀,這就是文章開頭所說的“默認編碼”。因此大家所說的Python中文字符問題就可以總結為一句話:當無法通過默認的字符編碼對字節(jié)進行轉換時了罪,就會出現(xiàn)解碼錯誤(UnicodeEncodeError)锭环。

Python2和Python3的解釋器使用的默認編碼是不一樣的,我們可以通過sys.getdefaultencoding()來獲取默認編碼:

>>># Python2

>>> import sys

>>> sys.getdefaultencoding()

'ascii'

>>># Python3

>>> import sys

>>> sys.getdefaultencoding()

'utf-8'

因此泊藕,對于Python2來講辅辩,Python解釋器在讀取到中文字符的字節(jié)碼嘗試解碼操作時,會先查看當前代碼文件頭部是否有指明當前代碼文件中保存的字節(jié)碼對應的字符編碼是什么娃圆。如果沒有指定則使用默認字符編碼"ASCII"進行解碼導致解碼失敗玫锋,導致如下錯誤:

SyntaxError: Non-ASCII character'\xc4'infile xxx.pyonline11, butnoencoding declared; see http://python.org/dev/peps/pep-0263/fordetails

對于Python3來講,執(zhí)行過程是一樣的讼呢,只是Python3的解釋器以"UTF-8"作為默認編碼撩鹿,但是這并不表示可以完全兼容中文問題。比如我們在Windows上進行開發(fā)時悦屏,Python工程及代碼文件都使用的是默認的GBK編碼节沦,也就是說Python代碼文件是被轉換成GBK格式的字節(jié)碼保存到磁盤中的。Python3的解釋器執(zhí)行該代碼文件時础爬,試圖用UTF-8進行解碼操作時甫贯,同樣會解碼失敗,導致如下錯誤:

SyntaxError: Non-UTF-8code startingwith'\xc4'infilexxx.pyonline11, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

3. 最佳實踐

創(chuàng)建一個工程之后先確認該工程的字符編碼是否已經(jīng)設置為UTF-8

為了兼容Python2和Python3幕帆,在代碼頭部聲明字符編碼:-*- coding:utf-8 -*-

四获搏、Python2與Python3中對字符串的支持

其實Python3中對字符串支持的改進,不僅僅是更改了默認編碼,而是重新進行了字符串的實現(xiàn)常熙,而且它已經(jīng)實現(xiàn)了對UNICODE的內置支持纬乍,從這方面來講Python已經(jīng)和JAVA一樣優(yōu)秀。下面我們來看下Python2與Python3中對字符串的支持有什么區(qū)別:

Python2

Python2中對字符串的支持由以下三個類提供

class basestring(object)

class str(basestring)

class unicode(basestring)

執(zhí)行help(str)和help(bytes)會發(fā)現(xiàn)結果都是str類的定義裸卫,這也說明Python2中str就是字節(jié)串仿贬,而后來的unicode對象對應才是真正的字符串。

#!/usr/bin/env python

# -*- coding:utf-8 -*-

a ='你好'

b =u'你好'

print(type(a), len(a))

print(type(b), len(b))

輸出結果:

(<type'str'>,6)

(<type'unicode'>,2)

Python3

Python3中對字符串的支持進行了實現(xiàn)類層次的上簡化墓贿,去掉了unicode類茧泪,添加了一個bytes類。從表面上來看聋袋,可以認為Python3中的str和unicode合二為一了队伟。

classbytes(object)

classstr(object)

實際上,Python3中已經(jīng)意識到之前的錯誤幽勒,開始明確的區(qū)分字符串與字節(jié)嗜侮。因此Python3中的str已經(jīng)是真正的字符串,而字節(jié)是用單獨的bytes類來表示啥容。也就是說锈颗,Python3默認定義的就是字符串,實現(xiàn)了對UNICODE的內置支持咪惠,減輕了程序員對字符串處理的負擔击吱。

#!/usr/bin/env python

# -*- coding:utf-8 -*-

a ='你好'

b =u'你好'

c ='你好'.encode('gbk')

print(type(a), len(a))

print(type(b), len(b))

print(type(c), len(c))

輸出結果:

2

2

4

五、字符編碼轉換

上面提到遥昧,UNICODE字符串可以與任意字符編碼的字節(jié)進行相互轉換覆醇,如圖:

那么大家很容易想到一個問題,就是不同的字符編碼的字節(jié)可以通過Unicode相互轉換嗎渠鸽?答案是肯定的叫乌。

Python2中的字符串進行字符編碼轉換過程是:

字節(jié)串-->decode('原來的字符編碼')-->Unicode字符串-->encode('新的字符編碼')-->字節(jié)串

#!/usr/bin/env python

# -*- coding:utf-8 -*-

utf_8_a ='我愛中國'

gbk_a = utf_8_a.decode('utf-8').encode('gbk')

print(gbk_a.decode('gbk'))

輸出結果:

我愛中國

Python3中定義的字符串默認就是unicode,因此不需要先解碼徽缚,可以直接編碼成新的字符編碼:

字符串-->encode('新的字符編碼')-->字節(jié)串

#!/usr/bin/env python

# -*- coding:utf-8 -*-

utf_8_a ='我愛中國'

gbk_a = utf_8_a.encode('gbk')

print(gbk_a.decode('gbk'))

輸出結果:

我愛中國

最后需要說明的是憨奸,Unicode不是有道詞典,也不是google翻譯器凿试,它并不能把一個中文翻譯成一個英文排宰。正確的字符編碼的轉換過程只是把同一個字符的字節(jié)表現(xiàn)形式改變了,而字符本身的符號是不應該發(fā)生變化的那婉,因此并不是所有的字符編碼之間的轉換都是有意義的板甘。怎么理解這句話呢?比如GBK編碼的“中國”轉成UTF-8字符編碼后详炬,僅僅是由4個字節(jié)變成了6個字節(jié)來表示盐类,但其字符表現(xiàn)形式還應該是“中國”,而不應該變成“你好”或者“China”。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末在跳,一起剝皮案震驚了整個濱河市枪萄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌猫妙,老刑警劉巖瓷翻,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異割坠,居然都是意外死亡齐帚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門彼哼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來对妄,“玉大人,你說我怎么就攤上這事沪羔〖⒁粒” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵蔫饰,是天一觀的道長。 經(jīng)常有香客問我愉豺,道長篓吁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任蚪拦,我火速辦了婚禮杖剪,結果婚禮上,老公的妹妹穿的比我還像新娘驰贷。我一直安慰自己盛嘿,他們只是感情好,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布括袒。 她就那樣靜靜地躺著次兆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锹锰。 梳的紋絲不亂的頭發(fā)上芥炭,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音恃慧,去河邊找鬼园蝠。 笑死,一個胖子當著我的面吹牛痢士,可吹牛的內容都是我干的彪薛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼善延!你這毒婦竟也來了训唱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤挚冤,失蹤者是張志新(化名)和其女友劉穎况增,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體训挡,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡澳骤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了澜薄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片为肮。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖肤京,靈堂內的尸體忽然破棺而出颊艳,到底是詐尸還是另有隱情,我是刑警寧澤忘分,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布棋枕,位于F島的核電站,受9級特大地震影響妒峦,放射性物質發(fā)生泄漏重斑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一肯骇、第九天 我趴在偏房一處隱蔽的房頂上張望窥浪。 院中可真熱鬧,春花似錦笛丙、人聲如沸漾脂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骨稿。三九已至,卻和暖如春蠢琳,著一層夾襖步出監(jiān)牢的瞬間啊终,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工傲须, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蓝牲,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓泰讽,卻偏偏與公主長得像例衍,于是被迫代替她去往敵國和親昔期。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內容