Python中的文字物件
Python 3.x中處理文字的物件有str, bytes, bytearray。
- bytes和bytearray可以使用除了用作格式化的方法(format, format_map)以及幾個特殊的基於Unicode的方法(casefold, isdecimal, isidentifier, isnumeric, isprintable, encode)以外幾乎所有str的方法。
- bytes有一個類方法,可以通過序列來構建字串,而這個方法不可以用在str上。
1234567>>> b = bytes.fromhex('E4 B8 AD')>>> bb'xe4xb8xad'>>> b.decode('utf-8')'中'>>> str(b)"b'\xe4\xb8\xad'"Unicode和字元轉換
採用chr可以把一個Unicode的code point轉換為字元,通過ord可以進行反向操作。
12345678>>> ord('A')65>>> ord('中')20013>>> chr(65)'A'>>> chr(20013)'中'
len函式計算的是字元數,不是位元組數
1 2 3 4 5 6 |
>>> len('中') 1 >>> '中'.encode('utf-8') b'xe4xb8xad' >>> len('中'.encode('utf-8')) #計算的是bytes物件的長度,包含3個整數字符 3 |
Python與編碼
Python內部處理編碼的方式
在Python接受我們的輸入時,總是會先轉為Unicode。而且這個過程越早越好。
然後Python的處理總是對Unicode進行的,在這個過程中,一定不要進行編碼轉換的工作。
在Python向我們返回結果時,總是會從Unicode轉為我們需要的編碼。而且這個過程越晚越好。
Python原始碼的編碼方式
Python預設使用utf-8編碼。
如果想使用一種不同的編碼方式來儲存Python程式碼,我們可以在每個檔案的第一行或者第二行(如果第一行被hash-bang命令佔用了)放置編碼宣告(encoding declaration)
# ‐*‐ coding: windows‐1252 ‐*‐
Python中使用的編碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
C:\Users\JL>chcp #查詢作業系統使用的編碼 Active code page: 936 >>> import sys, locale >>> locale.getpreferredencoding() #這個是最重要的 'cp936' >>> my_file = open('cafe.txt','r') >>> type(my_file) >>> my_file.encoding #檔案物件預設使用locale.getpreferreddecoding()的值 'cp936' >>> sys.stdout.isatty(), sys.stdin.isatty(), sys.stderr.isatty() #output是否是控制檯console (True, True, True) >>> sys.stdout.encoding, sys.stdin.encoding, sys.stderr.encoding #sys的標準控制流如果被重定向,或者定向到檔案,那麼編碼將使用環境變數PYTHONIOENCODING的值、控制檯console的編碼、或者locale.getpreferredencoding()的編碼,優先順序依次減弱。 ('cp936', 'cp936', 'cp936') >>> sys.getdefaultencoding() #如果Python需要把二進位制資料轉為字元物件,那麼在預設情況下使用該值。 'utf-8' >>> sys.getfilesystemencoding() #Python用來編碼或者解碼檔名(不是檔案內容)的時候,預設使用該編碼。 'mbcs' |
以上是在Windows中的測試結果,如果在GNU/Linux或者OSX中,那麼所有的結果都是UTF-8.
關於mbcs和utf-8的區別,可以參考http://stackoverflow.com/questions/3298569/difference-between-mbcs-and-utf-8-on-windows
檔案讀寫的編碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
>>> pen('cafe.txt','w',encoding='utf-8').write('café') 4 >>> fp = open('cafe.txt','r') >>> fp.read() 'caf茅' >>> fp.encoding 'cp936' >>> open('cafe.txt','r', encoding = 'cp936').read() 'caf茅' >>> open('cafe.txt','r', encoding = 'latin1').read() 'café' >>> fp = open('cafe.txt','r', encoding = 'utf-8') >>> fp.encoding 'utf-8' |
從上面的例子可以看出,無論什麼時候都不要使用預設的編碼,因為在不同的機器上執行的時候會出現意想不到的問題。
Python如何處理來自Unicode的麻煩
Python總是通過code point來比較字串的大小,或者是否相等的。
- Unicode中重音符號有兩種表示方法,用一個位元組表示,或者用基字母加上重音符號表示,在Unicode中他們是相等的,但是在Python中由於通過code point來比較大小,所以就不相等了。
123456>>> c1 = 'cafe\u0301'>>> c2 = 'café'>>> c1 == c2False>>> len(c1), len(c2)(5, 4)
解決方法是通過unicodedata庫中的normalize函式,該函式的第一個引數可以接受”NFC”,’NFD’,’NFKC’,’NFKD’四個引數中的一個。
NFC(Normalization Form Canonical Composition):以標準等價方式來分解,然後以標準等價重組之。若是singleton的話,重組結果有可能和分解前不同。儘可能的縮短整個字串的長度,所以會把’eu0301’2個位元組壓縮到一個位元組’é’。
NFD(Normalization Form Canonical Decomposition):以標準等價方式來分解
NFKD(Normalization Form Compatibility Decomposition):以相容等價方式來分解
NFKC(Normalization Form Compatibility Composition):以相容等價方式來分解,然後以標準等價重組之。
NFKC和NFKD可能會引起資料損失。
1 2 3 4 5 6 7 8 9 10 11 |
from unicodedata import normalize >>> c3 = normalize('NFC',c1) #把c1往字串長度縮短的方向操作 >>> len(c3) 4 >>> c3 == c2 True >>> c4 = normalize('NFD',c2) >>> len(c4) 5 >>> c4 == c1 True |
西方的鍵盤通常會鍵入儘可能短的字串,也就是說和”NFC”的結果一致,但是通過”NFC”來操作一下再比較字串是否相等比較安全。且W3C建議使用”NFC”的結果。
- 同樣的一個字元在Unicode中有兩個不同的編碼。
該函式會把一個單一的Unicode字元轉為另一個Unicode字元。
12345678910111213>>> o1 = '\u2126'>>> o2 = '\u03a9'>>> o1, o2('Ω', 'Ω')>>> o1 == o2False>>> name(o1), name(o2)('OHM SIGN', 'GREEK CAPITAL LETTER OMEGA')>>> o3 = normalize('NFC',o1)>>> name(o3)'GREEK CAPITAL LETTER OMEGA'>>> o3 == o2True
又比如
123456789>>> u1 = '\u00b5'>>> u2 = '\u03bc'>>> u1,u2('µ', 'μ')>>> name(u1), name(u2)('MICRO SIGN', 'GREEK SMALL LETTER MU')>>> u3 = normalize('NFKD',u1)>>> name(u3)'GREEK SMALL LETTER MU'
再一個例子
123456>>> h1 = '\u00bd'>>> h2 = normalize('NFKC',h1)>>> h1, h2('½', '1⁄2')>>> len(h1), len(h2)(1, 3) - 有時候我們希望使用不區分大小寫的形式進行比較
使用方法str.casefold(),該方法會把大寫字母轉換為小寫進行比較,比如’A’會轉為’a’,’MICRO SIGN’的’µ’會轉換為’GREEK SMALL LETTER MU’的’µ’
在絕大部分(98.9%)情況下str.casefold()和str.lower()的結果一致。 - 文字排序
由於不同的語言規則,如果單純按照Python的比較code point的方式進行,那麼會出現很多不是使用者期望的結果。
通常採用locale.strxfrm進行排序。
1234>>> import locale>>> locale.setlocale(locale.LC_COLLATE,'pt_BR.UTF-8')'pt_BR.UTF-8'>>> sort_result = sorted(intial, key = locale.strxfrm)編碼解碼錯誤
如果是Python原始碼中出現瞭解碼錯誤,那麼會產生SyntaxError異常。
其他情況下,如果發現編碼解碼錯誤,那麼會產生UnicodeEncodeError, UnicodeDecodeError異常。
幾個摘自fluent python中的有用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
from unicodedata import normalize, combining def nfc_equal(s1, s2): '''return True if string s1 is eual to string s2 after normalization under "NFC" ''' return normalize("NFC",s1) == normalize("NFC",s2) def fold_equal(s1, s2): '''return True if string s1 is eual to string s2 after normalization under "NFC" and casefold()''' return normalize('NFC',s1).casefold() == normalize('NFC',s2).casefold() def shave_marks(txt): '''Remove all diacritic marks basically it only need to change Latin text to pure ASCII, but this func will change Greek letters also below shave_latin_marks func is more precise''' normal_txt = normalize('NFD',txt) shaved = ''.join(c for c in normal_txt if not combining(c)) return normalize('NFC',shaved) def shave_latin_marks(txt): '''Remove all diacritic marks from Latin base characters''' normal_txt = normalize('NFD',txt) keeping = [] latin_base=False for c in normal_txt: if combining(c) and latin_base: continue #Ingore diacritic marks on Latin base char keeping.append(c) #If it's not combining char, it should be a new base char if not combining(c): latin_base = c in string.ascii_letters |
編碼探嗅Chardet
這是Python的標準模組。
參考資料: