#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) #➌
➊把所有字符分解成基字符和和组合记号
➋ 过滤掉所有组合记号
➌重组所有字符