python字元編碼慣用法

2022-09-23 14:30:13 字數 4265 閱讀 4877

在寫猥瑣寶典時需要總結soj上做過的題,準備在總結過程中順便寫乙個soj上的題解。題解使用python可讀,也就是python可以直接eval的格式,以便於處理。寫題解老是copy soj上的題目id,title不是太方便,所以就準備自動生成乙個空的題解,裡面包含了我做過的題。然而直接從soj上只能拿到自己過了的題的id列表,缺乏其它資訊。缺乏的資訊可以抽象為soj資料庫,其中包含了乙個以id為主鍵的表,表中有題目所有的資訊。於是**分為兩部分,一部分是soj的工具,其中包含了資料庫操作,根據id獲取ac的題目列表,另一部分是基於soj工具,負責題解資料的操作。

學python比較晚,當時面臨python2和python3的選擇,看過一些區別,感覺python3的設計更加合理,而python2比較隨意。尤其是字串部分,python2的概念不清楚,把位元組的序列和字串混在一起,導致一些混亂。首先用python3寫的工具,然後想把**作為web後台,把結果以網頁的形式展示出來,這就涉及到python2了,因為在生產環境中還是以python2為主。在轉為python2的過程中,不得不重新審視一下字串的部分,然後總結出這篇文章。

在python2中使用string和unicode string型別,而在python3中則使用bytes和string型別,他們對等:python2.string = python3.bytes, python2.unicode string = python3.string。在python2中,感覺字串相關東西混亂的原因在於命名失誤:以偏概全,用的是string的名表達的是bytes的概念。

bytes的概念:

bytes表達了位元組為單位的序列,資料本身的意義很有限,只有當這些資料是按一定規則組織的,進而表達了某個概念,才存在編碼問題,資料才有意義。

比如我們可以認為bytes每4位元組表示乙個32位整數,其中整數每連續8位作為乙個位元組放在一起,且低位放在前面,這樣的bytes就是有編碼的,表達了32位整數的概念。

同樣的我們也可以認為bytes表示了utf8編碼的字串,每1到6個位元組對應乙個unicode字元,我們稱bytes具有utf8編碼,表達了字串的概念。

string的概念:

如果要表達字串概念,[char]才是比較準確的(字元的列表)。至於乙個char占用多少儲存,完全不知道。可以是utf16,用2或4位元組表示乙個char,也可以是utf32用4位元組表達乙個char,還可以是utf8用1到6位元組來表示乙個char,這是實現者的事,不應該對我們的使用產生任何影響。

所以,我們需要bytes用來表示string時,需要指定編碼,將string轉為bytes,對應的函式是encode。當我們認為bytes具有某個字元編碼表達的是乙個字串的時候,通過decode並指定該編碼得到string。嚴格地說來在python2中,我們不應該在string物件上呼叫encode方法,不應該在unicode string物件上呼叫decode方法。

更進一步,任何抽象都可以encode得到對應的bytes,通過decode得到對應的抽象。

在上述的基礎上,引入一些編碼約定,目標是避免編碼錯誤。

1.源**編碼,這個編碼通過#coding:***指出,在python2和python3中概念都很明確。

慣用法(約定):

源**只使用utf8編碼。

2.string literal的型別和編碼:

python2中乙個形如"***x"的string literal具有string型別,編碼和源**編碼一致。

而形如u"***x"的string literal具有unicode string型別,存在乙個自動的源**編碼向unicode string decode的過程。無法轉換時會報錯,這個時候需要查通過注釋指出的編碼以及源**的真實編碼。

python3中乙個形如"***x"的string literal具有string型別,此時,存在自動的源**編碼向unicode的decode過程。

而b"***x"的string literal具有bytes型別,表示了源**的一部分。所以,當其中內容是字串時,我們也稱之是乙個和源**編碼一致的字串。

慣用法:

python2中用string來表達字串的概念,並使用utf8編碼。而python2中unicode string和string混用時,string被認為具有源**編碼,並decode為unicode string。於是存在陷阱,使用的變數的型別失去控制,不知道是string還是unicode string,所以在這裡要特別注意。如果用string表達字串的概念,同時用其它編碼,也是可以的,主要是看編碼對應的字符集和應用是不是能很好結合在一起。

而python3則string來表達字串的概念,不關心編碼問題。python3不存在混用問題,bytes和string一結合使用,就會報錯。

3.urlopen(***).read()後的處理:

很明確,這裡返回的概念是python3.bytes。

如果確定返回的東西是乙個網頁的文字,我們可以呼叫decode(encoding='網頁編碼', errors='ignore')來得到對應的字串。

但是,注意2中python2的約定,我們的字串的型別是python2.string的,所以在python2中我們還要有乙個encode的過程:encode(encoding='源**編碼', errors='ignore')

4.寫資料檔案:

在這裡寫的是乙個特殊的檔案,檔案可以被看成是一段python**並執行,所以:

慣用法:

檔案編碼使用utf8。

在python2中

with open(file, 'wb') as tempf:

tempf.write(data)

with open(file, 'w') as tempf:

tempf.write(data)

都是可以的,因為string無法區別是位元組還是字串。

而在python3中則要使用:

with open(file, 'wb') as tempf:

tempf.write(data.encode(encoding='utf8',errors='ignore'))

或with open(file, 'w') as tempf:

tempf.write(data)

因為前者寫入的是bytes而後者是string。

上述可行性是建立在我們約定資料檔案和源**編碼都是utf8的情況下得到的,如果沒有這些約定,我們看看這些**的語義:

python2中兩份**語義有點亂:

如果已知data是乙個字串,那麼第乙份**理論上錯誤,但是實際上兩份**都完成了把data寫到檔案,資料檔案編碼和data一致的目標。

如果已知data是乙個二進位製流,那麼第二份**理論上錯誤,但是實際上兩份**也都完成任務,資料檔案不存在編碼問題。

python3中第乙份**的語義是,寫入data這個字串,資料檔案編碼是utf8。

而第二份**語義是:寫入data這個字串,資料檔案編碼和當前原始檔編碼(在這是認為預設編碼等於當前原始檔編碼)一致。

5.讀資料檔案:

慣用法:

在python2中

with open(file, 'rb') as tempf:

tempf.read()

和with open(file, 'r') as tempf:

tempf.read()

均可,因為前者返回一些位元組,但是用的是string作容器。而後者返回的還是string。

而在python3中則要用:

with open(file, 'rb') as tempf:

tempf.read().decode(encoding='utf8',errors='ignore')

或with open(file, 'r') as tempf:

tempf.read()

因為開啟的模式不同,前者返回了bytes,後者返回的是string(在內部嘗試bytes嘗試用原始檔encoding解碼)。

同樣的這些**的可行性也是建立在若干約定的基礎上,如果沒有這些約定,也來看看其語義:

python2中第乙份**的語義是讀回一些二進位制資料。

python2中第二份**的語義是讀入一些字串。(理論上會有乙個先讀二進位制再根據當前預設編碼decode然後再向當前預設編碼encode的過程,但是在這裡認為兩個變換是恒等的,所以沒動作。顯然,在出現非法字元的情況下,兩個變換不恒等。)

python3中第乙份**的語義是讀入二進位制資料,根據utf8進行decode。

python3中第二份**的語義是隱式地讀入二進位制資料,根據當前預設編碼進行decode。

在我的應用場景裡,讀回的資料還有個eval的過程。顯然,python2直到eval才可能出現編碼錯誤,而python3能更早發現錯誤。

python字元編碼慣用法

本文總結在實際應用中遇到的python字元編碼問題,制定一套編碼相關的約定,避免編碼上的錯誤。在寫猥瑣寶典時需要總結soj上做過的題,準備在總結過程中順便寫乙個soj上的題解。題解使用python可讀,也就是python可以直接eval的格式,以便於處理。寫題解老是copy soj上的題目id,ti...

Python程式設計慣用法

一般交換兩個值是借助中間變數來實現,不過python有更簡單的實現方式 x,y y,x 這種方式耗時少,也更加簡潔。之所以能這樣,因為一般情況下python的表示式的計算順序是從左到右,但是遇到表示式賦值的時候,表示式右邊的操作會先於左邊的運算元計算,因此表示式 exp3,exp4 exp1,exp...

C 之RAII慣用法

c 中的raii全稱是 resource acquisition is initialization 直譯為 資源獲取就是初始化 但是這翻譯並沒有顯示出這個慣用法的真正內涵。raii的好處在於它提供了一種資源自動管理的方式,當產生異常 回滾等現象時,raii可以正確地釋放掉資源。舉個常見的例子 在資...