Unicode文本排序
Python比较任何类型的序列时,会一一比较序列里的各个元素。对字符串来说,比较的是码位,可是在比较非ASCII字符时,得到的结果不如人意。
fruits = ['caju', 'atemoia', 'caja', 'acai', 'acerola']
sorted(fruits)
['acai', 'acerola', 'atemoia', 'caja', 'caju']
在Python中,非ASCII文本的标准排序方式是使用locale.strxfrm函数,根据locale模块的文档,这个函数会把字符串转换成适合所在区域进行比较的形式
使用locale.strxfrm函数之前,必须为应用设定合适的区域设置,还要希望操作系统支持这项设置。
在使用locale.strxfrm函数做排序键之前,要调用setlocale(LC_COLLATE,your_locale)
- 区域设置时全局的,因此不推荐在库中调用setlocale函数。应用或框架应该在进程启动时设定区域设置,而且此后不要再修改
- 操作系统必须支持区域设定,否则setlocale函数会抛出locale.Error: unsupported locale setting异常
- 必须知道如何拼写区域名称。它在Unix衍生系统中几乎已经形成标准,要通过‘language_code.encoding'获取。但是在Windows中,句法复杂一些:
- 操作系统的制作者必须正确实现了所设区域
- 标准库提供的国际化排序方式可用,但是似乎只支持GNU/Linux。即使如此,还要依赖区域设置。
使用Unicode排序算法排序
通过PyUCA(Unicode Collation Algorithm,UCA)库,使用pyuca.Collator.sort_key方法
import pyuca
coll = pyuca.Collator()
fruits = ['caju', 'atemoia', 'caja', 'acai', 'acerola']
sorted_fruits = sorted(fruits, key=coll.sort_key)
sorted_fruits
['acai', 'acerola', 'atemoia', 'caja', 'caju']
PyUCA没有考虑区域设置,如果要定制排序方式,可以把自定义的排序表路径传给Collator()构造方法。PyUCA默认使用项目自带的allkey.txt
Unicode数据库
Unicode标准提供了一个完整的数据库(许多格式化的文本文件),不仅包括码位与字符名称之间的映射,还有各个字符的元数据,以及字符之间的关系。例如,Unicode数据库记录了字符是否可以打印、是不是字母、是不是数字,或者是不是其他数值符号。字符串的isdenfitier、isprintable、isdecimal和isnumeric等方法就是靠这些信息作为判断的。str.casefold方法也用到了Unicode表中的信息。
unicodedata模块中有几个函数用于获取字符的元数据。例如,字符在标准中的官方名称是不是组合字符(如结合波形符构成的变音符号等),以及符号对应的人类可读数值
# Unicode数据库中数值字符的元数据示例
import unicodedata
import re
re_digit = re.compile(r'\d')
sample = '1\xbc\xb2\u0969\u136b\u216b\u2466\u2480\u3285'
for char in sample:
print("U+%04x" % ord(char), # 输出U+0000格式的码位
char.center(6), # 在长度为6的字符串居中显示字符
're_dig' if re_digit.match(char) else '-' # 如果字符匹配正则表达式r'\d',显示re_dig,
'isdig' if char.isdigit() else '-', # 如果char.isdigit()返回True,显示isdig
'isnum' if char.isnumeric() else '-', # 如果char.isnumeric()返回True,显示isnum
format(unicodedata.numeric(char), '5.2f'), # 使用长度为5、小数点后保留两位的浮点数来显示数值
unicodedata.name(char), # Unicode标准中字符的名称
sep='\t') # 间隔为'\t'
U+0031 1 re_dig isnum 1.00 DIGIT ONE
U+00bc ¼ - isnum 0.25 VULGAR FRACTION ONE QUARTER
U+00b2 ² -isdig isnum 2.00 SUPERSCRIPT TWO
U+0969 ३ re_dig isnum 3.00 DEVANAGARI DIGIT THREE
U+136b ፫ -isdig isnum 3.00 ETHIOPIC DIGIT THREE
U+216b Ⅻ - isnum 12.00 ROMAN NUMERAL TWELVE
U+2466 ⑦ -isdig isnum 7.00 CIRCLED DIGIT SEVEN
U+2480 ⒀ - isnum 13.00 PARENTHESIZED NUMBER THIRTEEN
U+3285 ㊅ - isnum 6.00 CIRCLED IDEOGRAPH SIX
正则表达式r'\d'能匹配数字'1'和梵文数字'3',但是不能匹配isdigit方法判断为数字的其他字符。re模块对Unicode的支持并不充分。PyPI中有regex模块,它的最终目的是取代re模块,以提供更好的Unicode支持