程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

一篇文章理清python的字符編碼

編輯:Python

文章目錄
  • 1 字符編碼
  • 2 python 的字符串
    • python 源代碼
    • python 2.7 中的str和unicode
      • str
      • unicode
      • str和unicode之間的轉換

最近在用python接受網絡數據的時候,輸出時總是遇到編碼的問題,雖然都解決了,但深刻意識到自己其實對python的編碼並沒有清晰的認識,所以才會遇到這樣的問題。今天就此總結一下,以免日後夜長夢多。

1 字符編碼

首先,談一下什麼是字符編碼。先看看計算機是怎麼表示數字的,計算機使用二進制(為什麼?),最早的計算機在設計時采用8個比特(bit)作為一個字節(byte),所以,一個字節能表示的數字個數是256個,比如0~255(二進制11111111 = 十進制255),要想表示更大的數字,就得用更多的字節。也就是說,其實數字最終存在計算機中都是邏輯上的0和1的組合,不同的存儲介質有不同的物理表現,比如在磁盤中每個磁質單元的磁性表示位的信息。數字的表示是如此,那字符呢?之所以我們對數字存成二進制有比較直觀的理解,是因為二進制的概念在計算機出現之前早就有了,這是數學上的概念,而我們常用的十進制數字對應成二進制自然是比較簡單的。但是當碰到字符時,我們就不知道如何下手了。其實也很簡單,既然計算機能存數字,那肯定也能存字符,只要我們把字符和數字給對應上就行了,這個需要一套統一的規則進行對應,這樣使用者才能達成共識。

計算機是美國人發明的,所以最早的字符編碼也是他們規定的,只有127個字符被編碼到計算機裡,可以用來表示一些字母、數字和其他一些符號,這就是 ASCII碼 。

在當時來說,這些已經足夠了。但是如果要處理中文,這顯然是不夠的,所以中國制定了GB2312編碼。同理,其他國家也會有其他國家的編碼,因為使用的語言不同。很容易想到,這會有一個問題,就是當一個文本中出現多種語言時,該如何進行編碼?

所以,Unicode 就是用來解決這個問題的,把所有語言都統一到一個編碼裡面去,這樣就不會出現問題了。 Unicode中大部分的字符都是用兩個字節表示(除了一些比較生僻的字符),現代操作系統和大多數編程語言都直接支持Unicode。

比如字在ascii中是找不到對應編碼的,而在unicode中對應的十進制數為20013,表示成二進制就是01001110 00101101

很顯然,unicode比ascii是更加占用空間的,如果文本是中文或者混雜其他非英文語言的話,這是不可避免的,畢竟要編碼更多的字符,就得用更大的空間。但如果文本是英文的話,用unicode存儲會比ascii大一倍的空間,這顯然是不希望看到的。可能會有人想到可以用哈夫曼編碼,根據字符出現的頻率來決定各個字符的不同長度,這也不失為一種辦法,但是世界上這麼多字符,要如何統計呢?以哪些文本作為統計的依據?並且不同地區使用的字符頻率也不相同。但為了解決這個問題,還是有一種新的編碼方式被提出了,那就是utf-8,這種編碼采用更加靈活的變長方式,把一個Unicode字符根據不同的數字大小編碼成1-6個字節使得原來的ascii編碼能表示的字符,仍然按照原來的編碼進行,漢字通常是3個字節,只有很生僻的字符才會被編碼成4-6個字節。舉個栗子:

字符

ASCII

Unicode

UTF-8

A

01000001

00000000 01000001

01000001

x

01001110 00101101

11100100 10111000 10101101

這樣還有一個好處,就是utf-8編碼可以兼容以前使用ascii編碼的文本,解決一些歷史遺留問題。

現在計算機系統通用的字符編碼工作方式:在計算機內存中,統一使用Unicode編碼,當需要保存到硬盤或者需要傳輸的時候,就轉換為UTF-8編碼。浏覽網頁的時候,服務器會把動態生成的Unicode內容轉換為UTF-8再傳輸到浏覽器。

2 python 的字符串

理清了字符編碼的來龍去脈,我們再來看看python中字符串的編碼。

python 源代碼

首先,python的源代碼是文本文件,所以其保存和讀取是按一定的編碼進行的。保存時的編碼按照編輯器指定的保存編碼進行,那python解釋器在讀取源代碼時是按照什麼格式進行讀取的呢? 以python2.7為例,運行下面的代碼:

# 中文

沒錯,這只是一個注釋,其實中文不管出現在哪裡,都是一樣的,因為這個時候都只是被當成文本處理。運行之後會報以下錯誤: SyntaxError: Non-ASCII character '\xe4' in file F:/projects/pycharm/test/coding_test.py on line 2, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

說的是文件中存在非ascii字符,並且沒有指定編碼,所以解釋器無法識別該字符,點進去那個鏈接,可以看到詳情。裡面有這麼一句話:

Python will default to ASCII as standard encoding if no other encoding hints are given. 如果沒有其他編碼提示,python默認使用ASCII作為標准編碼。

保存的時候是按照utf-8編碼進行保存的,所以字符串中文在存儲中的表示就是'\xe4\xb8\xad\xe6\x96\x87'(實際上是二進制,這種十六進制表示是為了方便討論,將字節11100100表示為\xe4)。然而由於沒有指定編碼,所以python解釋器默認使用ASCII編碼進行讀取,遇到\xe4這樣的非ASCII字符自然無能為力了。所以需要我們手動對編碼進行指定,以確保跟保存時的編碼一致。指定的方式是在源文件的第一行或第二行進行注明,注明的字符串需滿足以下正則表達式: ^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)

常見的方式是:

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

加上這一行後,代碼就能正常運行了。

python 2.7 中的str和unicode

python 3 和 python 2 的字符編碼略有差別,這裡以2.7為例進行討論,理解了這個,再去看python 3 的其實也很好理解,都是類似的道理。注意以下的討論都是在python 2.7中進行的。

python 2.7 中,有兩種字符串類型,一種是str,一種是unicode,兩者的差別在於:

str is text representation in bytes, unicode is text representation in unicode characters(or unicode bytes).

意思就是,unicode的字符編碼類型是unicode,給出一個unicode字符串,我就會按unicode的方式去解碼,也就是說他表示的字符也確定了;但str不是這樣的,它只是一些字節,如果不知道編碼格式的話,那就不知道如何處理,只有最初打出來的人才能通過適當的編碼集進行解碼。而python在print一個str的時候是默認按照utf-8進行解碼的,所以當打印以下字符時,會出現亂碼:

s = '\xd6\xd0\xce\xc4'
print s

原因是以上的字節其實是字符串中文按照gbk編碼得到的結果,而默認用utf-8解碼進行打印時,自然就出現亂碼了,要想正常顯示,可以指定用gbk的方式進行解碼:

s = '\xd6\xd0\xce\xc4'
print s.decode('gbk')

這樣就能正常地打印出中文兩個字了。

str

當我們以引號的方式進行一個字符串字面量的聲明時,表示的是str類型,比如:

# -*- coding: utf-8 -*-
d = '中文'
print type(d)
print repr(d)

repr返回對象的canonical string(標准字符串)形式,當為str類型時,如果字符在ascii編碼范圍內,則顯示的是字符本身,否則,以\xXX的形式表示,其中XX為其十六進制表示。輸出的結果是:

<type 'str'>'\xe4\xb8\xad\xe6\x96\x87'

這裡的字節碼結果是采用utf-8進行編碼的,但這是不一定的,得看當前編輯器的設置。比如如果在命令行下運行以上python語句的話,出來的結果是'\xd6\xd0\xce\xc4',這是因為在該環境下是gbk編碼。所以當我們在處理字符串的時候,不能看表面顯示出來的字符,否則很容易出錯,我們看到的中文在不同環境下可能是不一樣的。

unicode

那如果要聲明一個unicode字符串怎麼做呢?只需要在字符串的引號前加一個u即可:

d = u'中文'
print type(d)
print repr(d)

輸出為: <type 'unicode'>u'\u4e2d\u6587'

str和unicode之間的轉換

str和unicode之間是可以進行轉換的,可以使用encode和decode方法。

encode encode的輸入必須是unicode類型,返回的一定是一個str類型,也就是將一個unicode字符串按照指定的編碼進行,轉成str。 輸出為:

decode decode的輸入必須是str類型,返回的一定是一個unicode類型,也就是將一個unicode字符串按照指定的編碼進行解碼,轉成unicode。 輸出為:

以上說encode的輸入必須是unicode類型,decode的輸入必須是str類型,那麼如果不是相應的類型,會怎麼樣?試一下吧:

d = u'中文aa'
print d.decode('utf-8')

然後就報錯了: UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128) 說的是ascii無法對位置0-1的字符進行編碼,為什麼會有編碼?我不是在解碼嗎?思考了之後,我有了一個合理的推測:如果decode輸入不是str類型,那麼會先將其轉為str,也就是說,會對其調用encode,並且此時由於沒有指定編碼,所以默認以ascii進行編碼,遇到中文就報錯了。可以做個試驗:

d = u'cc中文aa'
d.encode()

果然報的錯是一樣的: UnicodeEncodeError: 'ascii' codec can't encode characters in position 2-3: ordinal not in range(128)

再看看下面的代碼:

d = u'hello'
# print repr(d)
print repr(d.decode('utf-8'))
print repr(d.encode())

沒有報錯,輸出是:

u'hello'
'hello'

這樣看來,上面的推測是合理的。

以上就是對python編碼的總結,一邊查資料一邊思考一邊寫,有種豁然開朗的感覺。如果有任何錯誤,歡迎在評論區留言指正。

此外,在寫這篇文章的時候,對encode(‘base64’)和‘unicode-escape’還沒有完全搞明白,等以後有時間再總結。

參考: [1] 廖雪峰python教程:字符串和編碼 [2] 0和1 [3] Python encode和decode

補充: 對於encode輸入必須是unicode的問題,在這裡補充以下,encode(‘base64’)是個例外,其輸入為str。 試驗如下:

s = u'hhe哈eh'
print type(s.encode('base64'))

報錯: UnicodeEncodeError: 'ascii' codec can't encode character u'\u54c8' in position 3: ordinal not in range(128)

從結果來看,程序對s進行了ascii編碼,那只能猜想其先對s做了一次encode,並且默認為ascii編碼,再次試驗:

s = u'hhe哈eh'
print type(s.encode().encode('base64'))

報的錯誤是一樣的: UnicodeEncodeError: 'ascii' codec can't encode character u'\u54c8' in position 3: ordinal not in range(128) 改為:

s = u'hhe哈eh'
print s.encode('utf-8').encode('base64')
print type(s.encode('utf-8').encode('base64'))

輸出:

aGhl5ZOIZWg=
<type 'str'>

可見encode(‘base64’)的輸入為str時可以正常,而為unicode時會將其先進行一次encode轉為str(默認采用ascii,如果出現非ascii字符會報錯),所以可以推測其輸入應該為str。

其實再跑一下下面的代碼就明白了:

print 'aGhl5ZOIZWg='.decode('base64')
print type('aGhl5ZOIZWg='.decode('base64'))

輸出:

print 'aGhl5ZOIZWg='.decode('base64')
print type('aGhl5ZOIZWg='.decode('base64'))

可見decode的結果也不一定是unicode,在使用base64解碼時,其值仍然是str。之所以base64編碼解碼的輸入和輸出都是str,可能與base64的編碼規則有關。

相關文章:Unicode(UTF-8, UTF-16)令人混淆的概念


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved