在使用Python或者其餘的編程語言,都會多多少少遇到編碼錯誤,處理起來很是痛苦。在Stack Overflow和其餘的編程問答網站上,UnicodeDecodeError和UnicodeEncodeError也常常被說起。本篇教程但願能幫你認識Python編碼,並可以從容的處理編碼問題。python
本教程提到的編碼知識並不限定在Python,其餘語言也大同小異,但咱們依然會以Python爲主,來演示和講解編碼知識。git
經過該教程,你將學習到以下的知識:編程
如今的編碼規則已經有好多了,最簡單、最基本是的ASCII編碼,只要是你學過計算機相關的課程,你就應該多少了解一點ASCII編碼,他是最小也是最適合瞭解字符編碼原理的編碼規則。具體以下:windows
那麼,字符編碼的定義究竟是什麼了?它是一種將字符(如字母,標點符號,符號,空格和控制字符)轉換爲整數並最終轉換爲bit進行存儲的方法。 每一個字符均可以編碼爲惟一的bit序列。 若是你對bit的概念不瞭解,請不要擔憂,咱們後面會介紹。編程語言
ASCII碼的字符被分爲以下幾組:函數
ASCII表一共包括128個字符,若是你想了解整個ASCII表,這裏有學習
你們在學python的時候確定會遇到不少難題,以及對於新技術的追求,這裏推薦一下咱們的Python學習扣qun:784,758,214,這裏是python學習者彙集地網站
string模塊是python裏處理字符串很方便的模塊,它包括了整個ASCII字符,讓咱們來看看部分string模塊源碼:ui
# From lib/python3.7/string.py whitespace = ' \t\n\r\v\f' ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz' ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ascii_letters = ascii_lowercase + ascii_uppercase digits = '0123456789' hexdigits = digits + 'abcdef' + 'ABCDEF' octdigits = '01234567' punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~""" printable = digits + ascii_letters + punctuation + whitespace
你能夠在Python中這樣使用string模塊:編碼
>>> import string >>> s = "What's wrong with ASCII?!?!?" >>> s.rstrip(string.punctuation) 'What's wrong with ASCII'
學過計算機相關課程的同窗,應該都知道,bit是計算機內部存儲單位,只有0和1兩個狀態(二進制),咱們上面所說的ASCII表,都是一個10進制的數字表示一個字符,而這個10進制數字,最終會轉換成0和1,存儲在計算機內部。例如(第一列是10進制數字,第二列是二進制,第三列是計算機內部存儲結果):
這是一種在Python中將ASCII字符串表示爲位序列的方便方法。 ASCII字符串中的每一個字符都被僞編碼爲8位,8位序列之間有空格,每一個字符表明一個字符:
>>> def make_bitseq(s: str) -> str: ... if not s.isascii(): ... raise ValueError("ASCII only allowed") ... return " ".join(f"{ord(i):08b}" for i in s) >>> make_bitseq("bits") '01100010 01101001 01110100 01110011' >>> make_bitseq("CAPS") '01000011 01000001 01010000 01010011' >>> make_bitseq("$25.43") '00100100 00110010 00110101 00101110 00110100 00110011' >>> make_bitseq("~5") '01111110 00110101'
咱們也能夠是用python的f-string 來格式化,好比f"{ord(i):08b}":
冒號的左側是ord(i),它是實際的對象,其值將被格式化並插入到輸出中。 使用ord()爲單個str字符提供了base-10代碼點。
冒號的右側是格式說明符。 08表示寬度爲8,0填充,b用做在基數2(二進制)中輸出結果數的符號。
ASCII採用的是8bit來存儲字符(只使用7位,剩下的1位二進制爲0),因此,ASCII最多存儲128個字符,這有個簡單的公式,計算存儲字符的bit數量與存儲字符總數的關係:2的n次方,n表示bit數量。例如:
咱們能夠寫個簡單的代碼,來計算一下,指定字符數量,至少須要多少bit來存儲:
>>> from math import ceil, log >>> def n_bits_required(nvalues: int) -> int: ... return ceil(log(nvalues) / log(2)) >>> n_bits_required(256) 8
在上面的ASCII討論中,您看到每一個字符映射到0到127範圍內的整數。但在CPython中還有其餘的數字系統,經過其餘方式是表示數字。除了十進制外,python還支持如下幾個方式:
你可能要問,爲何有了十進制,還要支持這麼多其餘進制的數字了?這個取決你的業務場景和操做系統,在Python裏,把str轉換成int,默認是10進制的。
>>> int('11') 11 >>> int('11', base=10) # 10 is already default 11 >>> int('11', base=2) # Binary 3 >>> int('11', base=8) # Octal 9 >>> int('11', base=16) # Hex 17
你能夠在賦值時,直接告訴解釋器數字的類型,不一樣進制標表示方法以下:
類型 | 前綴 | 示例 |
---|---|---|
n/a | n/a | 11 |
二進制 | 0b 或者 0B | 0b11 |
八進制 | 0o 或者 0O | 0o11 |
十六進制 | 0x 或者 0X | 0x11 |
>>> 11 11 >>> 0b11 # 二進制 3 >>> 0o11 # 八進制 9 >>> 0x11 # 16進制 17
正如您所看到的,ASCII的問題在於它不是一個足夠大的字符集來容納世界上的語言,方言,符號和字形。 (這對於英語來講甚至都不夠大。)Unicode從根本上起到與ASCII相同的做用,可是Unicode擁有更大的存儲空間,具備1,114,112個可能的字符,可以徹底包含世界上全部的語言。事實上,ASCII是Unicode的完美子集。 Unicode表中的前128個字符與您合理指望的ASCII字符徹底對應。
Unicode自己不是編碼,可是有不少遵循Unicode編碼規範編碼,後面講到的UTF-8就是其中一個。
Unicode是一種抽象編碼標準,而不是編碼。這就是UTF-8和其餘編碼方案發揮做用的地方。 Unicode標準(字符到代碼點的映射)從其單個字符集定義了幾種不一樣的編碼。UTF-8及其較少使用的表兄弟UTF-16和UTF-32是用於將Unicode字符表示爲每一個字符一個或多個字節的二進制數據的編碼格式。咱們稍後將討論UTF-16和UTF-32,但到目前爲止,UTF-8佔據了最大份額。
Python 3的str類型用於表示人類可讀的文本,能夠包含任何Unicode字符。
相反,字節類型表示二進制數據或原始字節序列,它們本質上沒有附加編碼。
編碼和解碼是從一個到另外一個的過程:
decode 和 encode 函數,默認編碼是utf-8:
>>> "résumé".encode("utf-8") b'r\xc3\xa9sum\xc3\xa9' >>> "El Niño".encode("utf-8") b'El Ni\xc3\xb1o' >>> b"r\xc3\xa9sum\xc3\xa9".decode("utf-8") 'résumé' >>> b"El Ni\xc3\xb1o".decode("utf-8") 'El Niño'
str.encode()的結果是一個bytes對象,bytes對象只容許ASCII字符。這就是爲何在調用「ElNiño」.encode(「utf-8」)時,容許ASCII兼容的「El」按原樣表示,但帶有波浪號的n被轉義爲「\ xc3 \ xb1」。 這個看起來很亂的序列表明兩個字節,十六進制爲0xc3和0xb1:
>>> " ".join(f"{i:08b}" for i in (0xc3, 0xb1)) '11000011 10110001'
默認狀況下,Python 3源代碼假定爲UTF-8。 這意味着您不須要# - * - 編碼:UTF-8 - * - 位於Python 3中.py文件的頂部。
默認狀況下,全部文本(str)都是Unicode。 編碼的Unicode文本表示爲二進制數據(字節)。 str類型能夠包含任何文字Unicode字符,例如「Δv/Δt」,全部這些字符都將存儲爲Unicode。
Unicode字符集中的任何內容都是標識符中的猶太符號,這意味着résumé=「〜/ Documents / resume.pdf」是有效的,雖然這看起來很花哨。
Python的re模塊默認爲re.UNICODE標誌而不是re.ASCII。 這意味着,例如,r「\ w」匹配Unicode字符,而不只僅是ASCII字母。
str.encode()和bytes.decode()中的默認編碼是UTF-8。
還有一個更細微的屬性,即內置的open()的默認編碼是依賴於平臺的,而且取決於locale.getpreferredencoding()的值:
>>> # Mac OS X High Sierra >>> import locale >>> locale.getpreferredencoding() 'UTF-8' >>> # Windows Server 2012; other Windows builds may use UTF-16 >>> import locale >>> locale.getpreferredencoding() 'cp1252'
一個關鍵特性是UTF-8是一種可變長度編碼。回想一下關於ASCII的部分。 擴展ASCII-land中的全部內容最多須要一個字節的空間。 您可使用如下生成器表達式快速證實這一點:
>>> all(len(chr(i).encode("ascii")) == 1 for i in range(128)) True
UTF-8徹底不一樣。 給定的Unicode字符能夠佔用1到4個字節。 如下是佔用四個字節的單個Unicode字符的示例:
>>> ibrow = "🤨" >>> len(ibrow) 1 >>> ibrow.encode("utf-8") b'\xf0\x9f\xa4\xa8' >>> len(ibrow.encode("utf-8")) 4 >>> # Calling list() on a bytes object gives you >>> # the decimal value for each byte >>> list(b'\xf0\x9f\xa4\xa8') [240, 159, 164, 168]
這是len()的一個微妙但重要的特性:
咱們來聊聊UTF-16和UTF-32,在實際的編程實踐中,它們和UTF-8區別仍是很重要的,下面的經過實例咱們來看看具體區別:
>>> letters = "αβγδ" >>> rawdata = letters.encode("utf-8") >>> rawdata.decode("utf-8") 'αβγδ' >>> rawdata.decode("utf-16") # '뇎닎돎듎'
在這種狀況下,使用UTF-8編碼四個希臘字母而後解碼回UTF-16中的文本將產生一個徹底不一樣語言(韓語)的文本str。 此表彙總了UTF-8,UTF-16和UTF-32下的字節範圍或字節數:
編碼 | 長度(字節) | 是否可變 |
---|---|---|
UTF-8 | 1~4 | 是 |
UTF-16 | 2~4 | 是 |
UTF-32 | 4 | 否 |
UTF系列編碼另一個須要注意的地方是,UTF-8編碼佔用存儲空間不必定比UTF-16少,由於他們都不是固定長度的。例如
>>> text = "記者 鄭啟源 羅智堅" >>> len(text.encode("utf-8")) 26 >>> len(text.encode("utf-16")) 22
緣由是U + 0800到U + FFFF(十進制的2048到65535)範圍內的代碼點佔用了UTF-8中的三個字節,而UTF-16中僅佔用了兩個字節。 正常狀況下,最好不用使用UTF-16,除非特殊要求,否則UTF-8更加通用。
Python內置了不少與編碼相關的函數:
能夠分紅如下幾組:
目前,咱們講了4中編碼:
還有其餘不少編碼,好比Latin-1(也稱做ISO-8859-1),這是HTTP默認的編碼,然而windows是Latin-1變體,稱做cp1252。 完整的已接受編碼列表隱藏在編解碼器模塊的文檔中,該模塊是Python標準庫的一部分。
還有一個有用的公認編碼須要注意,即「unicode-escape」。 若是您有一個已解碼的str並但願快速得到其轉義的Unicode文字的表示,那麼您能夠在.encode()中指定此編碼:
>>> alef = chr(1575) # Or "\u0627" >>> alef_hamza = chr(1571) # Or "\u0623" >>> alef, alef_hamza ('ا', 'أ') >>> alef.encode("unicode-escape") b'\\u0627' >>> alef_hamza.encode("unicode-escape") b'\\u0623'
雖然Python代碼默認使用了UTF-8做爲編碼,但並不意味着外部輸入的數據也是UTF-8編碼的,若是這些外部的數據沒有指定編碼,那麼在處理他們是,你就要格外當心了。好比你調用API獲取數據,正常是用UTF-8去解碼,沒有問題,但若是忽然API給你返回這樣的數據:
>>> data = b"\xbc cup of flour" >>> data.decode("utf-8") Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbc in position 0: invalid start byte
這地方拋出了UnicodeDecodeError錯誤,仔細檢查,發現其實數據的編碼是Latin-1。
>>> data.decode("latin-1") '¼ cup of flour'
若是你對字符串的編碼不肯定,可使用chardet庫來檢查字符串編碼。
在本文中你已經瞭解了編碼的詳細原理,相信你在之後的編程過程當中,再遇到編碼錯誤,相信你能比較從容的解決了。