1. 程式人生 > >Python中的字串與字元編碼

Python中的字串與字元編碼

本節內容:

  1. 前言
  2. 相關概念
  3. Python中的預設編碼
  4. Python2與Python3中對字串的支援
  5. 字元編碼轉換

一、前言

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

Python2的 預設編碼 是ASCII,不能識別中文字元,需要顯式指定字元編碼;Python3的 預設編碼 為Unicode,可以識別中文字元。

相信大家在很多文章中都看到過類似上面這樣“對Python中中文處理”的解釋,也相信大家在最初看到這樣的解釋的時候確實覺得明白了。可是時間久了之後,再重複遇到相關問題就會覺得貌似理解的又不是那麼清楚了。如果我們瞭解上面說的預設編碼的作用是什麼,我們就會更清晰的明白那句話的含義。

需要說明的是,“字元編碼是什麼”,以及“字元編碼的發展過程” 不是本節討論的話題,這些內容可以參考我之前的 <<這篇文章>>

二、相關概念

1. 字元與位元組

一個字元不等價於一個位元組,字元是人類能夠識別的符號,而這些符號要儲存到計算的儲存中就需要用計算機能夠識別的位元組來表示。一個字元往往有多種表示方法,不同的表示方法會使用不同的位元組數。這裡所說的不同的表示方法就是指字元編碼,比如字母A-Z都可以用ASCII碼錶示(佔用一個位元組),也可以用UNICODE表示(佔兩個位元組),還可以用UTF-8表示(佔用一個位元組)。字元編碼的作用就是將人類可識別的字元轉換為機器可識別的位元組碼,以及反向過程。

UNICDOE才是真正的字串,而用ASCII、UTF-8、GBK等字元編碼表示的是位元組串。關於這點,我們可以在Python的官方文件中經常可以看到這樣的描述"Unicode string" , " translating a Unicode string into a sequence of bytes"

我們寫程式碼是寫在檔案中的,而字元是以位元組形式儲存在檔案中的,因此當我們在檔案中定義個字串時被當做位元組串也是可以理解的。但是,我們需要的是字串,而不是位元組串。一個優秀的程式語言,應該嚴格區分兩者的關係並提供巧妙的完美的支援。JAVA語言就很好,以至於瞭解Python和PHP之前我從來沒有考慮過這些不應該由程式設計師來處理的問題。遺憾的是,很多程式語言試圖混淆“字串”和“位元組串”,他們把位元組串當做字串來使用,PHP和Python2都屬於這種程式語言。最能說明這個問題的操作就是取一個包含中文字元的字串的長度:

  • 對字串取長度,結果應該是所有字串的個數,無論中文還是英文
  • 對字串對應的位元組串取長度,就跟編碼(encode)過程使用的字元編碼有關了(比如:UTF-8編碼,一箇中文字元需要用3個位元組來表示;GBK編碼,一箇中文字元需要2個位元組來表示)

注意:Windows的cmd終端字元編碼預設為GBK,因此在cmd輸入的中文字元需要用兩個位元組表示

>>> # Python2
>>> a = 'Hello,中國'  # 位元組串,長度為位元組個數 = len('Hello,')+len('中國') = 6+2*2 = 10
>>> b = u'Hello,中國'  # 字串,長度為字元個數 = len('Hello,')+len('中國') = 6+2 = 8
>>> c = unicode(a, 'gbk')  # 其實b的定義方式是c定義方式的簡寫,都是將一個GBK編碼的位元組串解碼(decode)為一個Uniocde字串
>>> 
>>> print(type(a), len(a))
(<type 'str'>, 10)
>>> print(type(b), len(b))
(<type 'unicode'>, 8)
>>> print(type(c), len(c))
(<type 'unicode'>, 8)
>>>

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

2. 編碼與解碼

先做下科普:UNICODE字元編碼,也是一張字元與數字的對映,但是這裡的數字被稱為程式碼點(code point), 實際上就是十六進位制的數字。

Python官方文件中對Unicode字串、位元組串與編碼之間的關係有這樣一段描述:

Unicode字串是一個程式碼點(code point)序列,程式碼點取值範圍為0到0x10FFFF(對應的十進位制為1114111)。這個程式碼點序列在儲存(包括記憶體和物理磁碟)中需要被表示為一組位元組(0到255之間的值),而將Unicode字串轉換為位元組序列的規則稱為編碼。

這裡說的編碼不是指字元編碼,而是指編碼的過程以及這個過程中所使用到的Unicode字元的程式碼點與位元組的對映規則。這個對映不必是簡單的一對一對映,因此編碼過程也不必處理每個可能的Unicode字元,例如:

將Unicode字串轉換為ASCII編碼的規則很簡單--對於每個程式碼點:

  • 如果程式碼點數值<128,則每個位元組與程式碼點的值相同
  • 如果程式碼點數值>=128,則Unicode字串無法在此編碼中進行表示(這種情況下,Python會引發一個UnicodeEncodeError異常)

將Unicode字串轉換為UTF-8編碼使用以下規則:

  • 如果程式碼點數值<128,則由相應的位元組值表示(與Unicode轉ASCII位元組一樣)
  • 如果程式碼點數值>=128,則將其轉換為一個2個位元組,3個位元組或4個位元組的序列,該序列中的每個位元組都在128到255之間。

簡單總結:

  • 編碼(encode):將Unicode字串(中的程式碼點)轉換特定字元編碼對應的位元組串的過程和規則
  • 解碼(decode):將特定字元編碼的位元組串轉換為對應的Unicode字串(中的程式碼點)的過程和規則

可見,無論是編碼還是解碼,都需要一個重要因素,就是特定的字元編碼。因為一個字元用不同的字元編碼進行編碼後的位元組值以及位元組個數大部分情況下是不同的,反之亦然。

三、Python中的預設編碼

1. Python原始碼檔案的執行過程

我們都知道,磁碟上的檔案都是以二進位制格式存放的,其中文字檔案都是以某種特定編碼的位元組形式存放的。對於程式原始碼檔案的字元編碼是由編輯器指定的,比如我們使用Pycharm來編寫Python程式時會指定工程編碼和檔案編碼為UTF-8,那麼Python程式碼被儲存到磁碟時就會被轉換為UTF-8編碼對應的位元組(encode過程)後寫入磁碟。當執行Python程式碼檔案中的程式碼時,Python直譯器在讀取Python程式碼檔案中的位元組串之後,需要將其轉換為UNICODE字串(decode過程)之後才執行後續操作。

上面已經解釋過,這個轉換過程(decode,解碼)需要我們指定檔案中儲存的位元組使用的字元編碼是什麼,才能知道這些位元組在UNICODE這張萬國碼和統一碼中找到其對應的程式碼點是什麼。這裡指定字元編碼的方式大家都很熟悉,如下所示:

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

2. 預設編碼

那麼,如果我們沒有在程式碼檔案開始的部分指定字元編碼,Python直譯器就會使用哪種字元編碼把從程式碼檔案中讀取到的位元組轉換為UNICODE程式碼點呢?就像我們配置某些軟體時,有很多預設選項一樣,需要在Python直譯器內部設定預設的字元編碼來解決這個問題,這就是文章開頭所說的“預設編碼”。因此大家所說的Python中文字元問題就可以總結為一句話:當無法通過預設的字元編碼對位元組進行轉換時,就會出現解碼錯誤(UnicodeEncodeError)

Python2和Python3的直譯器使用的預設編碼是不一樣的,我們可以通過sys.getdefaultencoding()來獲取預設編碼:

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

>>> # Python3
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'

因此,對於Python2來講,Python直譯器在讀取到中文字元的位元組碼嘗試解碼操作時,會先檢視當前程式碼檔案頭部是否有指明當前程式碼檔案中儲存的位元組碼對應的字元編碼是什麼。如果沒有指定則使用預設字元編碼"ASCII"進行解碼導致解碼失敗,導致如下錯誤:

SyntaxError: Non-ASCII character '\xc4' in file xxx.py on line 11, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

對於Python3來講,執行過程是一樣的,只是Python3的直譯器以"UTF-8"作為預設編碼,但是這並不表示可以完全相容中文問題。比如我們在Windows上進行開發時,Python工程及程式碼檔案都使用的是預設的GBK編碼,也就是說Python程式碼檔案是被轉換成GBK格式的位元組碼儲存到磁碟中的。Python3的直譯器執行該程式碼檔案時,試圖用UTF-8進行解碼操作時,同樣會解碼失敗,導致如下錯誤:

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

3. 最佳實踐

  • 建立一個工程之後先確認該工程的字元編碼是否已經設定為UTF-8
  • 為了相容Python2和Python3,在程式碼頭部宣告字元編碼:-*- coding:utf-8 -*-

四、Python2與Python3中對字串的支援

其實Python3中對字串支援的改進,不僅僅是更改了預設編碼,而是重新進行了字串的實現,而且它已經實現了對UNICODE的內建支援,從這方面來講Python已經和JAVA一樣優秀。下面我們來看下Python2與Python3中對字串的支援有什麼區別:

Python2

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

class basestring(object)
    class str(basestring)
    class unicode(basestring)

執行help(str)和help(bytes)會發現結果都是str類的定義,這也說明Python2中str就是位元組串,而後來的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中對字串的支援進行了實現類層次的上簡化,去掉了unicode類,添加了一個bytes類。從表面上來看,可以認為Python3中的str和unicode合二為一了。

class bytes(object)
class str(object)

實際上,Python3中已經意識到之前的錯誤,開始明確的區分字串與位元組。因此Python3中的str已經是真正的字串,而位元組是用單獨的bytes類來表示。也就是說,Python3預設定義的就是字串,實現了對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))

輸出結果:

<class 'str'> 2
<class 'str'> 2
<class 'bytes'> 4

五、字元編碼轉換

上面提到,UNICODE字串可以與任意字元編碼的位元組進行相互轉換,如圖:

那麼大家很容易想到一個問題,就是不同的字元編碼的位元組可以通過Unicode相互轉換嗎?答案是肯定的。

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

位元組串-->decode('原來的字元編碼')-->Unicode字串-->encode('新的字元編碼')-->位元組串

#!/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('新的字元編碼')-->位元組串

#!/usr/bin/env python
# -*- coding:utf-8 -*-


utf_8_a = '我愛中國'
gbk_a = utf_8_a.encode('gbk')
print(gbk_a.decode('gbk'))

輸出結果:

我愛中國

最後需要說明的是,Unicode不是有道詞典,也不是google翻譯器,它並不能把一箇中文翻譯成一個英文。正確的字元編碼的轉換過程只是把同一個字元的位元組表現形式改變了,而字元本身的符號是不應該發生變化的,因此並不是所有的字元編碼之間的轉換都是有意義的。怎麼理解這句話呢?比如GBK編碼的“中國”轉成UTF-8字元編碼後,僅僅是由4個位元組變成了6個位元組來表示,但其字元表現形式還應該是“中國”,而不應該變成“你好”或者“China”。

前面花了很大的篇幅介紹概念和理論,後面注重實踐,希望對他人有所幫助。

相關推薦

Python字串字元編碼

本節內容: 前言 相關概念 Python中的預設編碼 Python2與Python3中對字串的支援 字元編碼轉換 一、前言 Python中的字元編碼是個老生常談的話題,同行們都寫過很多這方面的文章。有的人云亦云,也有的寫得很深入。近日看到某知名培訓機構的教學視訊中再次談及此問題,講解的還是不盡人意,所

Python字串字元編碼編碼和轉換問題

本節內容: 前言相關概念Python中的預設編碼Python2與Python3中對字串的支援字元編碼轉換 一、前言 Python中的字元編碼是個老生常談的話題,同行們都寫過很多這方面的文章。有的人云亦云,也有的寫得很深入。近日看到某知名培訓機構的教學視訊中再次談及此問題,講解的還是不盡人意,所以才想寫這篇

Python字串datetime的相互轉換

1. 字串轉換成datetime物件 from datetime import datetime t = datetime.strptime(append_at, '%Y-%m-%d %H:%M:%S') 結果顯示: <class 'datetime.datetime

js --- 字串unicode編碼

 1、charAt():把字串分成每一個字元,從左往右提取指定位置的字元 var str = '天氣'; alert( str.charAt(1) );           &nbs

python字串:宣告、編碼、函式、格式化

字串的宣告有三種方式:單引號、雙引號和三引號(包括三個單引號或三個雙引號)。例如: ? 1 2 3 4 5 6 7 8 9 10 11 12 >>> str1= 'hello world'  >>> str2= "hello

python字串列表轉換

首先是這樣的,看書的時候書上是這麼寫的: 當時腦子裡只有一個想法,字串轉為字串列表不是用split()嘛,為了解開謎團,就決定試一試,畢竟自己以前從來沒有注意到這個問題。 結果: 的確可以是字串,列表元素是字串字元。看來我真的注意到的問題太少了。。。。。。 補充: 字串變列表: st

pythonstrlist互換,txt檔案的讀取,字串變成列表操作,另存為TXT時從列表變成字串

file = open ("wider_face_train_bbx_gt.txt") for line in lines: print(type(line)) #<type 'str'> #78 221 7 8 2 0 0 0 0

python位元組字串的轉換

#bytes object     byte = b"byte example"     # str object     str = "str example"   &nbs

Python字串的find()方法index()方法

find()方法 語法 str1.find(str2, beg=0, end=len(string)) 作用 檢測 str2 是否包含在字串str1中,如果指定範圍 beg 和 end ,則檢查是否包含在指定範圍內,如果包含返回開始的索引值,否則返回-1。 i

Python字串的isalnum()方法、isalpha()方法isdigit()方法

isalnum()方法 語法: str.isalnum() 作用: 如果字串至少有一個字元並且所有字元都是字母或數字則返回 True,否則返回 False。 isalpha()方法 語法: str.isalpha() 作用: 如果字串至少有一個字元並且所有

python字串編碼方式小結

         Python2中字串的型別有兩種:str和unicode,其中unicode是統一編碼方式,它使得字元跟二進位制是一一對應的,因此所有其他編碼的encode都從unicode開始,而其他編碼方式按照相應的編碼decode之後也會變成unico

python字串編碼轉換

字串編碼轉換程式設計師最苦逼的地方,什麼亂碼之類的幾乎都是由漢字引起的。 其實編碼問題很好搞定,只要記住一點: 任何平臺的任何編碼,都能和Unicode互相轉換。 UTF-8與GBK互相轉換,那就先把UTF-8轉換成Unicode,再從Unicode轉換成GBK,反之同理。 注意:在python3中en

python字串拆分合併——split()、join()、strip()和replace()

Python3 split()方法 描述 split()通過指定分隔符對字串進行切片,如果引數num 有指定值,則僅分隔 num 個子字串 語法 split()方法語法: str.split(str="", num=string

python字串url編碼的轉換

主要應用的場景 爬蟲生成帶搜尋詞語的網址 1.字串轉為url編碼 import urllib poet_name = "李白" url_code_name = urllib.quote(p

python的u'字串"(字元編碼):字串前有u,表示字串以unicode格式儲存

舉個例子 >>> s = u'\u6ce8\u91ca' >>> s u'\u6ce8\u91ca' >>> print s 註釋 >>

python字串編碼和解碼

### 1. 常用的編碼 - ASCII:只能表示一些字母,數字和特殊的字元,佔一個位元組 - GBK:國家簡體中文字符集和繁體字符集,相容ASCII,佔兩個位元組 - Unicode:能夠表示全世界上所有的字元,Unicode有人說佔4個位元組也有人說佔2個位元組,但中文佔2個位元組 - UTF-8:Uni

python文字符串編碼問題

err har .json 情況 comm 常用 class not 字符串類型 接口測試的時候,發現接口返回內容是uncodie類型但是包含中文。在使用print進行打印時輸出提示錯誤: UnicodeEncodeError: ‘ascii‘ codec can‘t e

Pythonossys模塊的區別

mov clear maintain n) 安裝 文件 join() pat 系統 os與sys模塊的官方解釋如下: os: This module provides a portable way of using operating system dependent

python字母ascii碼的相互轉換

需要 編碼 方法 由於 nic 否則 python int bsp 在做python編程時,碰到了需要將字母轉換成ascii碼的,原本以為用Int()就可以直接將字符串轉換成整形了,可是int()帶了一個默認參數,base=10,這裏表示的是十進制,若出現字母,則會報錯,認

Pythonossys兩模塊的區別

des ren 異常類 函數 出現 輸出 func lena info <os和sys的官方解釋> ?os os: This module provides a portable way of using operating system depe