Python字符編碼之理解

在從普通程序員進(jìn)階到優(yōu)秀程序員的路上蚕钦,字符編碼是一個(gè)不得不跨過(guò)去的坎沧卢,我們幾乎所有的程序都會(huì)涉及到字符處理讥脐,如果跨不過(guò)這個(gè)坎,那么幾乎注定會(huì)面對(duì)一些坑幔戏。
本篇文章試圖通過(guò)實(shí)際的例子來(lái)闡釋字符編碼解碼的過(guò)程玛追,從而能夠更加清晰地認(rèn)識(shí)程序到底是怎樣處理字符的。在進(jìn)入正文之前,你需要先了解字符集和字符編碼的區(qū)別痊剖,需要知道什么是Unicode韩玩,什么是UTF-8,GBK等基本概念陆馁,如果你不了解找颓,請(qǐng)移步下面的幾篇文章:
字符編碼詳解
字符編碼筆記
之后,我們?cè)囅胍幌碌撸绦蛱幚碜址倪^(guò)程是怎樣的叮雳?我想最開(kāi)始一定是先打開(kāi)一個(gè)編輯器想暗,把程序?qū)懗鰜?lái)妇汗,然后將程序保存為一個(gè)源文件(Python中就是.py文件),所以我們先從文件的存儲(chǔ)開(kāi)始說(shuō)起说莫。

源文件的存儲(chǔ)

我們?cè)诰庉嬈髦袑懙拇a都是字符形式存在的杨箭,而當(dāng)我們要將這些字符存儲(chǔ)到硬盤時(shí),必須有一個(gè)編碼過(guò)程储狭,因?yàn)橛?jì)算機(jī)只能認(rèn)識(shí)0/1序列互婿,所以這些字符就必須通過(guò)一些編碼規(guī)則轉(zhuǎn)化成二進(jìn)制序列,然后再存儲(chǔ)到硬盤辽狈。比如我們寫了下面一段程序

s = '你好'
print repr(s), s

當(dāng)我們存儲(chǔ)該文件時(shí)慈参,如果是以GB2312編碼方式進(jìn)行存儲(chǔ)的,那么文件的二進(jìn)制表示是這樣的

?  testProgram hexdump -C gb2312encodingfile.py
00000000  73 20 3d 20 27 c4 e3 ba  c3 27 0a 70 72 69 6e 74  |s = '....'.print|
00000010  20 72 65 70 72 28 73 29  2c 20 73 0a              | repr(s), s.|
0000001c

這里73代表s
20代表空格
3d代表=
27代表'
c4 e3代表
ba c3代表刮萌,以此類推
在這里可以看出漢字在GB2312中是用兩個(gè)字節(jié)來(lái)表示的驮配。
我們?cè)偈褂胾tf-8來(lái)存儲(chǔ)同樣的一段代碼,看看其二進(jìn)制表示是什么樣子

?  testProgram hexdump -C utf8encodingfile.py
00000000  73 20 3d 20 27 e4 bd a0  e5 a5 bd 27 0a 70 72 69  |s = '......'.pri|
00000010  6e 74 20 72 65 70 72 28  73 29 2c 20 73 0a        |nt repr(s), s.|
0000001e

同樣的這里73代表s
20代表空格
3d代表=
27代表'
但是你好漢字是用三個(gè)字節(jié)來(lái)表示的
e4 bd a0代表
e5 a5 bd代表

現(xiàn)在源文件已經(jīng)以二進(jìn)制碼流存儲(chǔ)到了硬盤着茸,那么源代碼又是如何執(zhí)行的呢壮锻?

源代碼執(zhí)行

源代碼執(zhí)行的時(shí)候,Python解釋器首先會(huì)將源文件load進(jìn)內(nèi)存當(dāng)中涮阔,然后一行行開(kāi)始讀取文件并解釋執(zhí)行猜绣。

但是這里需要注意的是,如果是str字符串敬特,python解釋器只會(huì)讀取其二進(jìn)制碼流掰邢,假設(shè)我們使用的是gb2312encodingfile.py,那么s指向的字符串你好讀進(jìn)內(nèi)存后的表示就是c4 e3 ba c3, 當(dāng)我們使用print打印的時(shí)候伟阔,如果是在Windows的console上執(zhí)行辣之,則可以正確執(zhí)行,顯示如下

?  testProgram python gb2312encodingfile.py
'\xc4\xe3\xba\xc3' 你好

但是在Linux上或者mac上無(wú)法正確執(zhí)行减俏,顯示如下:

?  testProgram python gb2312encodingfile.py
'\xc4\xe3\xba\xc3' ???

這是由于Windows console默認(rèn)是GBK編解碼的(GB2312的擴(kuò)展)召烂,所以可以將\xc4\xe3\xba\xc3正確解碼顯示成漢字你好,但是在Linux或者M(jìn)ac上娃承,console的默認(rèn)編解碼方式是UTF-8奏夫,所以也就無(wú)法將\xc4\xe3\xba\xc3正確顯示出來(lái)怕篷。

另外一個(gè)小插曲是,如果代碼中有漢字酗昼,需要在文件開(kāi)頭聲明編碼方式(#-*- coding: utf-8 - 或者# coding=utf8)廊谓,否則解釋器默認(rèn)使用ASCII編碼方式去打開(kāi)源文件,這樣就會(huì)報(bào)錯(cuò)麻削,如下

?  testProgram python gb2312encodingfile.py
  File "gb2312encodingfile.py", line 1
SyntaxError: Non-UTF-8 code starting with '\xc4' in file gb2312encodingfile.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

但是如果我們的字符串是unicode對(duì)象的字符串蒸痹,那么Python解釋器會(huì)將字符串的字節(jié)序列先進(jìn)行解碼,然后再將解碼后的字節(jié)序列的引用賦給s呛哟,可以更改utf8encodingfile.py代碼如下:

#-*- coding: utf-8 -*-
s = '你好'
print repr(s), s

u = u'你好'
print repr(u), u

保存后叠荠,使用hexdump查看其二進(jìn)制編碼如下:

?  testProgram hexdump -C utf8encodingfile.py
00000000  23 2d 2a 2d 20 63 6f 64  69 6e 67 3a 20 75 74 66  |#-*- coding: utf|
00000010  2d 38 20 2d 2a 2d 0a 73  20 3d 20 27 e4 bd a0 e5  |-8 -*-.s = '....|
00000020  a5 bd 27 0a 70 72 69 6e  74 20 72 65 70 72 28 73  |..'.print repr(s|
00000030  29 2c 20 73 0a 0a 75 20  3d 20 75 27 e4 bd a0 e5  |), s..u = u'....|
00000040  a5 bd 27 0a 70 72 69 6e  74 20 72 65 70 72 28 75  |..'.print repr(u|
00000050  29 2c 20 75 0a                                    |), u.|
00000055

仔細(xì)觀察會(huì)發(fā)現(xiàn)兩個(gè)你好字符串都編碼成了e4 bd a0 e5 a5 bd
然后在mac上執(zhí)行,結(jié)果如下:

?  testProgram python utf8encodingfile.py
'\xe4\xbd\xa0\xe5\xa5\xbd' 你好
u'\u4f60\u597d' 你好

可以看出s指向的字節(jié)序列是\xe4\xbd\xa0\xe5\xa5\xbd扫责,而u指向的字節(jié)序列是\u4f60\u597d (也就是將e4 bd a0 e5 a5 bd 解碼成了\u4f60\u597d)

但是如果我們更改的是gb2312encodingfile.py榛鼎,并使用gb2312編碼保存,再執(zhí)行這個(gè)程序看看會(huì)是什么結(jié)果鳖孤。
結(jié)果直接報(bào)錯(cuò):

?  testProgram python gb2312encodingfile.py
  File "gb2312encodingfile.py", line 5
    u = u'???'
SyntaxError: (unicode error) 'utf8' codec can't decode byte 0xc4 in position 0: invalid continuation byte

這是由于Python解釋器嘗試用聲明的utf-8編碼方式去解碼gb2312編碼的字節(jié)序列者娱,所以造成了這樣的錯(cuò)誤。

至此我們已經(jīng)知道了Python如何讀寫源文件的苏揣,那么Python執(zhí)行的時(shí)候又是如何讀寫外部文件的呢黄鳍?

文件讀寫

現(xiàn)在我們使用如下代碼嘗試將字符串寫到文件當(dāng)中,注意源碼保存使用utf-8, 文件名為utf8encodingfile_write.py

#-*- coding: utf-8 -*-
s = '你好'
with open('stroutput.txt', 'w') as f:
    f.write(s)

u = u'你好'
with open('unicodeoutput.txt', 'w') as f:
    f.write(u)

在mac上執(zhí)行平匈,結(jié)果如下:

?  testProgram python utf8encodingfile_write.py
Traceback (most recent call last):
  File "utf8encodingfile_write.py", line 8, in <module>
    f.write(u)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)說(shuō)明系統(tǒng)在寫文件編碼的時(shí)候嘗試使用ASCII來(lái)進(jìn)行編碼框沟,可是我們明明已經(jīng)聲明了使用utf-8啊。

原來(lái)文件頭聲明使用utf-8吐葱,只是用于解釋器去解釋源碼文件的時(shí)候使用街望,當(dāng)我們調(diào)用write去寫一個(gè)文件的時(shí)候,會(huì)調(diào)用系統(tǒng)默認(rèn)的編碼設(shè)置來(lái)進(jìn)行編碼弟跑。我們來(lái)看下系統(tǒng)默認(rèn)的編碼是什么:

>>> import sys
>>> sys.getdefaultencoding()
'ascii'

果然灾前,系統(tǒng)默認(rèn)就是ascii編碼方式。
解決這個(gè)問(wèn)題有兩種方式孟辑,一種是修改系統(tǒng)默認(rèn)的編碼方式哎甲,另一種是在open的時(shí)候指定編碼方式,其中第二種顯然更加優(yōu)雅一些饲嗽。

# 通過(guò)修改系統(tǒng)默認(rèn)編碼方式來(lái)實(shí)現(xiàn)utf-8編碼
import sys
reload(sys) # 這里必須reload一下才能找到setdefaultencoding method
sys.setdefaultencoding('utf-8')


# 通過(guò)在codecs.open中設(shè)置編碼方式
import codecs

with codecs.open("filename", "w", encoding="utf-8") as f:
    f.write(u)

同樣的炭玫,當(dāng)我們讀取一個(gè)文件的時(shí)候,也可以通過(guò)codecs.open來(lái)設(shè)定編解碼方式貌虾,但是首先我們需要知道這個(gè)要讀取的文件的編碼方式吞加,假設(shè)文件是以u(píng)tf-8的方式進(jìn)行編碼的,讀取的時(shí)候就可以如下:

import codecs

with open("somefile", "r", encoding="utf-8") as f:
    content = f.read()
    

另外,有時(shí)我們并非從文件中讀取衔憨,而是直接使用了一個(gè)非標(biāo)準(zhǔn)字符叶圃,這是就需要使用decode先解碼

# if not decode, will raise exception: 'ascii' codec can't
# decode byte 0xe2 in position 0: ordinal not in range(128)
dash = '–'.decode("utf8")
if dash in title:
    title = title.split(dash)[0]

至此,關(guān)于Python編碼就講完了践图,如果你有收獲掺冠,就請(qǐng)點(diǎn)個(gè)贊鼓勵(lì)下吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末码党,一起剝皮案震驚了整個(gè)濱河市德崭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌揖盘,老刑警劉巖眉厨,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異扣讼,居然都是意外死亡缺猛,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門椭符,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人耻姥,你說(shuō)我怎么就攤上這事销钝。” “怎么了琐簇?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵蒸健,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我婉商,道長(zhǎng)似忧,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任丈秩,我火速辦了婚禮盯捌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蘑秽。我一直安慰自己饺著,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布肠牲。 她就那樣靜靜地躺著幼衰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缀雳。 梳的紋絲不亂的頭發(fā)上渡嚣,一...
    開(kāi)封第一講書(shū)人閱讀 51,610評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼识椰。 笑死扬绪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的裤唠。 我是一名探鬼主播挤牛,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼种蘸!你這毒婦竟也來(lái)了墓赴?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤航瞭,失蹤者是張志新(化名)和其女友劉穎诫硕,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體刊侯,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡章办,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了滨彻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藕届。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亭饵,靈堂內(nèi)的尸體忽然破棺而出休偶,到底是詐尸還是另有隱情,我是刑警寧澤辜羊,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布踏兜,位于F島的核電站,受9級(jí)特大地震影響八秃,放射性物質(zhì)發(fā)生泄漏碱妆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一昔驱、第九天 我趴在偏房一處隱蔽的房頂上張望疹尾。 院中可真熱鬧,春花似錦舍悯、人聲如沸航棱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)饮醇。三九已至,卻和暖如春秕豫,著一層夾襖步出監(jiān)牢的瞬間朴艰,已是汗流浹背观蓄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祠墅,地道東北人侮穿。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像毁嗦,于是被迫代替她去往敵國(guó)和親亲茅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 字符集和編碼簡(jiǎn)介 在編程中常彻纷迹可以見(jiàn)到各種字符集和編碼克锣,包括ASCII,MBCS,Unicode等字符集。確切的說(shuō)...
    蘭山小亭閱讀 8,494評(píng)論 0 13
  • 可以看我的博客 lmwen.top 或者訂閱我的公眾號(hào) 簡(jiǎn)介有稍微接觸python的人就會(huì)知道腔长,python中...
    ayuLiao閱讀 3,117評(píng)論 1 5
  • 編碼問(wèn)題一直困擾著開(kāi)發(fā)人員袭祟,尤其在 Java 中更加明顯,因?yàn)?Java 是跨平臺(tái)語(yǔ)言捞附,不同平臺(tái)之間編碼之間的切換...
    x360閱讀 2,480評(píng)論 1 20
  • http://python.jobbole.com/85231/ 關(guān)于專業(yè)技能寫完項(xiàng)目接著寫寫一名3年工作經(jīng)驗(yàn)的J...
    燕京博士閱讀 7,579評(píng)論 1 118
  • 小伙伴們巾乳,大家好! 我叫袁琳鸟召,來(lái)自成都胆绊。我的三個(gè)標(biāo)簽是 1.2個(gè)小朋友的媽媽 2.科技公司會(huì)計(jì) 3.對(duì)自我要求比較...
    薪薪小午媽閱讀 163評(píng)論 0 0