在寫猥瑣寶典時需要總結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可以正確地釋放掉資源。舉個常見的例子 在資...