規範化Unicode字符串
因爲Unicode有組合字符(變音符號和附加到前一個字符上的記號,打印時作爲一個整
體),所以字符串比較起來很複雜。
#café”這個詞可以使用兩種方式構成,分別有4個和5個碼位,但是結果完全一樣:
>>> s1 = 'café'
>>> s2 = 'cafe\u0301'
>>> s1, s2
('café', 'café')
>>> len(s1), len(s2)
(4, 5)
>>> s1 == s2
False
分析:U+0301是COMBINING ACUTE ACCENT,加在“e”後面得到“é”。在Unicode標準中,'é’和’e\u0301’這樣的序列叫“標準等價物”(canonical equivalent),應用程序應該把它們視作相同的字符。但是,Python看到的是不同的碼位序列,因此判定二者不相等。
NFC(Normalization Form C)使用最少的碼位構成等價的字符串;
NFD把組合字符分解成基字符和單獨的組合字符;
這兩種規範化方式都能讓比較行爲符合預期:
>>> from unicodedata import normalize
>>> s1 = 'café' # 把"e"和重音符組合在一起
>>> s2 = 'cafe\u0301' # 分解成"e"和重音符
>>> len(s1), len(s2)
(4, 5)
>>> len(normalize('NFC', s1)), len(normalize('NFC', s2))
(4, 4)
>>> len(normalize('NFD', s1)), len(normalize('NFD', s2))
(5, 5)
>>> normalize('NFC', s1) == normalize('NFC', s2)
True
>>> normalize('NFD', s1) == normalize('NFD', s2)
True
在另外兩個規範化形式(NFKC和NFKD)的首字母縮略詞中,字母K表示“compatibility”
(兼容性)。這兩種是較嚴格的規範化形式,對“兼容字符”有影響。
大小寫摺疊
大小寫摺疊其實就是把所有文本變成小寫,再做些其他轉換。這個功能由str.casefold()方法(Python 3.3新增)支持。
>>> micro = 'µ'
>>> name(micro)
'MICRO SIGN'
>>> micro_cf = micro.casefold()
>>> name(micro_cf)
'GREEK SMALL LETTER MU'
>>> micro, micro_cf
('µ', 'μ')
>>> eszett = 'ß'
>>> name(eszett)
'LATIN SMALL LETTER SHARP S'
>>> eszett_cf = eszett.casefold()
>>> eszett, eszett_cf
('ß', 'ss')
分析:對於只包含latin1字符的字符串s,s.casefold()得到的結果與s.lower()一樣,除了兩個特殊的字符:微符號’µ’會變成小寫的希臘字母“μ”(在多數字體中二者看起來一樣);德語 Eszett(“sharp s”,ß)會變成“ss”。
規範化文本匹配實用函數
對大多數應用來說,NFC是最好的規範化形式。不區分大小寫的比較應該使用str.casefold()。
如果要處理多語言文本,應該有nfc_equal和fold_equal函數。
比較規範化Unicode字符串
"""
Utility functions for normalized Unicode string comparison.
Using Normal Form C, case sensitive:
>>> s1 = 'café'
>>> s2 = 'cafe\u0301'
>>> s1 == s2
False
>>> nfc_equal(s1, s2)
True
>>> nfc_equal('A', 'a')
False
Using Normal Form C with case folding:
>>> s3 = 'Straße'
>>> s4 = 'strasse'
>>> s3 == s4
False
>>> nfc_equal(s3, s4)
False
>>> fold_equal(s3, s4)
True
>>> fold_equal(s1, s2)
True
>>> fold_equal('A', 'a')
True
"""
from unicodedata import normalize
def nfc_equal(str1, str2):
return normalize('NFC', str1) == normalize('NFC', str2)
def fold_equal(str1, str2):
return (normalize('NFC', str1).casefold() ==
normalize('NFC', str2).casefold())
極端規範化:去掉變音符號
#去掉全部組合記號的函數
import unicodedata
import string
def shave_marks(txt):
"""去掉全部變音符號"""
norm_txt = unicodedata.normalize('NFD', txt) #➊
shaved = ''.join(c for c in norm_txt
if not unicodedata.combining(c)) #➋
return unicodedata.normalize('NFC', shaved) #➌
➊把所有字符分解成基字符和和組合記號
➋ 過濾掉所有組合記號
➌重組所有字符