使用 unicodedata 模块对字符串标准化

简介: 使用 unicodedata 模块对字符串标准化

本次我们来聊一聊 Python 的一个内置模块:unicodedata,它是专门用来处理 unicode 字符串的,下面来看看它的用法。

unicodedata.normalize

负责对 unicode 字符串进行规范化处理,因为有些字符看似长度为 1,但其实不是,举个例子:

s1 = "é"
s2 = "é"
s3 = "e\u0301"
print(len(s1))  # 1
print(len(s2))  # 2
print(len(s3))  # 2
print(s1 == s2)  # False
print(s2 == s3)  # True
print(s1, s2, s3)  # é é é

相信打印结果会让你感到诧异,s2 的长度看似为 1,但其实是 2,这是因为 Python3 的字符串存储的其实是码位序列。而某些字符可以有多种表示方式,比如 é:

  • 预组合字符:代码中的 s1,这是一个单一的 unicode 码点,表示带有重音符号的小写字母 e;
  • 分解序列:代码中的 s3,将小写字母 e 和重音符号(\u0301)分开,使用两个 unicode 码点表示,而 s2 虽然看起来和 s1 一样,但它的表示方式和 s3 是相同的;

# 两个码点
s = "é"
print(s[0] == "e")  # True
print(s[1] == "\u0301")  # True

这种情况直接处理的话会比较头疼,所以 normalize 的作用就是规范化,让多个码点表示的字符使用一个码点表示。规范化前后的字符串长得一样,但是长度变短了。

import unicodedata
s1 = "lové"
s2 = "lové"
s3 = "love\u0301"
print(s1 == s2 == s3)  # False
print(len(s1), len(s2), len(s3))  # 4 5 5
# 对字符串进行规范化,第一个参数是规范化方式,第二个参数是字符串
# "NFC":将多个码点表示的字符替换为等效的单个码点形式
normalize_s2 = unicodedata.normalize("NFC", s2)
normalize_s3 = unicodedata.normalize("NFC", s3)
print(
    s1 == normalize_s2 == normalize_s3
)  # True
print(s2, normalize_s2)  # lové lové
print(len(s2), len(normalize_s2))  # 5 4

规范化方式除了 NFC 之外还有 NFD,它和 NFC 是相反的,表示让一个码点表示的字符使用多个码点表示。

import unicodedata
s1 = "lové"
s2 = "lové"
s3 = "love\u0301"
print(len(s1), len(s2), len(s3))  # 4 5 5
# s1 中的 é 使用一个码点表示,这里让它变成等效的两个码点
normalize_s1 = unicodedata.normalize("NFD", s1)
print(len(normalize_s1))  # 5
print(s1 == s3)  # False
print(normalize_s1 == s3)  # True

因此这就是 unicode 的规范化处理,因为有的 unicode 字符可以有多种表示方式,可以是一个码点,也可以是两个码点,但它们长得都一样。NFC 是让两个码点表示的字符使用一个码点表示,NFD 是让一个码点表示的字符使用两个码点表示。

但需要注意:如果字符只有一种表示方式,那么规范化前后的结果是一样的。

import unicodedata
# 这六个字符都只有一种表示方式
s = "satori"
nfc_s = unicodedata.normalize("NFC", s)
nfd_s = unicodedata.normalize("NFD", s)
print(s == nfc_s == nfd_s)  # True
# 这个 emoji 同样只有一种表示方式,并且占用两个码点
s = "😀"
nfc_s = unicodedata.normalize("NFC", s)
nfd_s = unicodedata.normalize("NFD", s)
print(s == nfc_s == nfd_s)  # True

所以 NFC 相当于组合,NFD 相当于分解。

然后除了 NFC 和 NFD 之外,还有 NFKC 和 NFKD,它们有什么区别呢?用大白话解释就是:

  • NFC 和 NFD 标准化前后的字符,虽然占用的码点不同,但起码长得一样,两者在外观上是等价的。这种替换方式也被称为标准替换。
  • 而 NFKC 和 NFKD 则是兼容替换,它们更关注文字语义的表达。
import unicodedata
print(unicodedata.normalize("NFKD", "㍿"))  # 株式会社
print(unicodedata.normalize("NFKD", "㊥"))  # 中
print(
    unicodedata.normalize("NFKD", "①②③④⑤")
)  # 12345
# 上述几个字符串如果使用 NFC、NFD 标准化,那么结果还是原来的样子
# 而 '㍿' 和 '株式会社' 显然不是一个东西,它们长的都不一样
# 所以这两者不可能是同一个字符的不同表达形式
# 但从人类的思维来说,这两者就是一个东西,在语义上是等价的
# 所以 NFKD 是兼容替换,说白了就是按照语义替换
# 比如全角转半角,组合字符分解成多个独立字符
comma1 = ","
comma2 = ","
print(
    unicodedata.normalize("NFD", comma1) == comma2
)  # False
print(
    unicodedata.normalize("NFKD", comma1) == comma2
)  # True
# comma1 是中文的逗号,comma2 是英文的逗号
# 采用 NFD 标准化的结果和之前一样,因为全角和半角压根不是一个东西
# 但对于人类而言,一眼就知道它们都是逗号
# 所以 NFKD 标准化之后,会将中文逗号转成英文逗号

所以这几种规范化方式区别如下:

  • NFC:某些字符可以有多种表达方式,将多个码点表示的字符转成使用一个码点表示,字符在替换前后的外观是一样的;
  • NFD:和 NFC 相反,将一个码点表示的字符转成使用多个码点表示,字符在替换前后的外观也是一样的;
  • NFKD:按照语义对字符进行兼容分解(全角转半角,组合字符分解),前后外观会发生变化,但现实语义不变。比如 ㊥ 和 中,㊋ 和 火;
  • NFKC:NFKD 是兼容分解,直接就完事了,而 NFKC 还会进行组合;

大部分情况下,我们只需要使用 NFC 规范化即可,而 NFKD 在处理带圆圈的数字的时候也会使用。

unicodedata.category

该函数可以返回一个字符的类别,而类别有以下几种:

'Lu':'大写字母(Letter, uppercase)'
'Ll':'小写字母(Letter, lowercase)'
'Lt':'标题大小写字母(Letter, titlecase)'
'Lm':'修饰字母(Letter, modifier)'
'Lo':'其他字母(Letter, other)'
'Mn':'非间断标记(Mark, nonspacing)'
'Mc':'间断标记(Mark, spacing combining)'
'Me':'封闭标记(Mark, enclosing)'
'Nd':'十进制数字(Number, decimal digit)'
'Nl':'字母数字(Number, letter)'
'No':'其他数字(Number, other)'
'Pc':'连接符号(Punctuation, connector)'
'Pd':'破折号符号(Punctuation, dash)'
'Ps':'开放的标点符号(Punctuation, open)'
'Pe':'关闭的标点符号(Punctuation, close)'
'Pi':'初引号(Punctuation, initial quote)'
'Pf':'末引号(Punctuation, final quote)'
'Po':'其他标点符号(Punctuation, other)'
'Sm':'数学符号(Symbol, math)'
'Sc':'货币符号(Symbol, currency)'
'Sk':'修饰符号(Symbol, modifier)'
'So':'其他符号(Symbol, other)'
'Zs':'空格符号(Separator, space)'
'Zl':'分行符(Separator, line)'
'Zp':'分段符(Separator, paragraph)'
'Cc':'控制字符(Other, control)'
'Cf':'格式字符(Other, format)'
'Cs':'代理字符(Other, surrogate)'
'Co':'私用字符(Other, private use)'
'Cn':'未分配字符(Other, not assigned)'

举个例子:

import unicodedata
print(unicodedata.category('A'))  # Lu
print(unicodedata.category('a'))  # Ll
print(unicodedata.category('1'))  # Nd
print(unicodedata.category('$'))  # Sc
print(unicodedata.category(' '))  # Zs

'A' 是大写字母,所以它的类别是 'Lu';'a' 是小写字母,所以它的类别是 'Ll';'1' 是十进制数字,所以它的类别是 'Nd';'$' 是货币符号,所以它的类别是 'Sc';' ' 是空格符号,所以它的类别是 'Zs'。

unicodedata.lookup

有些 unicode 字符是有名字的,可以根据它的名字查找相应的字符。

import unicodedata
print(
    unicodedata.lookup("LATIN SMALL LETTER A"),
    unicodedata.lookup("COPYRIGHT SIGN"),
    unicodedata.lookup("PEACH"),
)  # a © 🍑

如果给定的名字不是一个有效的 Unicode 字符名,那么会抛出 KeyError。

unicodedata.name

和 lookup 功能相反,负责返回字符的名称。

import unicodedata
print(unicodedata.name("z"))  # LATIN SMALL LETTER Z
print(unicodedata.name("@"))  # COMMERCIAL AT
print(unicodedata.name("🍑"))  # PEACH

比较简单,如果字符没有名称,则抛出 ValueError,或者也可以指定一个默认值。

unicodedata.numeric

将 unicode 字符转成等效的数值,如果无法转换则返回默认值(没有则抛出 ValueError)。

import unicodedata
print(unicodedata.numeric("零"))  # 0.0
print(unicodedata.numeric("〇"))  # 0.0
print(unicodedata.numeric("一"))  # 1.0
print(unicodedata.numeric("贰"))  # 2.0
print(unicodedata.numeric("叁"))  # 3.0
print(unicodedata.numeric("四"))  # 4.0
print(unicodedata.numeric("伍"))  # 5.0
print(unicodedata.numeric("⑥"))  # 6.0
print(unicodedata.numeric("漆"))  # 7.0
print(unicodedata.numeric("捌"))  # 8.0
print(unicodedata.numeric("玖"))  # 9.0
print(unicodedata.numeric("拾"))  # 10.0
text = "加 v́ 壹捌⑤壹零贰❤️捌⑥捌〇②,看 Python❤️"
def chr_to_num(char):
    try:
        return str(unicodedata.numeric(char))[0]
    except ValueError:
        return char
print(
    "".join(map(chr_to_num, text))
)  # 加 v́ 185102❤️86802,看 Python❤️

以上就是 unicodedata 模块的一些用法,当然还有几个函数没有说,个人觉得用不上。这里面最有用的应该就是 normalize 函数了,更多内容可以参考官网。

相关文章
|
1月前
|
自动驾驶 物联网 5G
|
3月前
|
XML 测试技术 API
SFNC —— 标准特征命名约定(一)
SFNC —— 标准特征命名约定(一)
50 3
|
2月前
|
SQL 分布式计算 大数据
代码编码原则和规范大数据开发
此文档详细规定了SQL代码的编写规范,包括代码的清晰度,执行效率,以及注释的必要性。它强调所有SQL关键字需统一使用大写或小写,并禁止使用select *操作。此外,还规定了代码头部的信息模板,字段排列方式,INSERT, SELECT子句的格式,运算符的使用,CASE语句编写规则,查询嵌套规范,表别名定义,以及SQL注释的添加方法。这些规则有助于提升代码的可读性和可维护性。
44 0
|
6月前
再见手动编码,标准自动化编码规则来帮忙!
标准管理员小S面临数据标准编码管理的挑战:编码格式不统一、编码值不可读活相关性差,手动管理耗时易出错。Dataphin新推出“标准编码规则”功能,可以实现一次配置批量生成编码,并通过自增序列、固定字符串和所属标准集编码的组合,保证编码相关性和灵活性,同时提供了编码规则变更后的批量订正功能,大大简化管理工作。小S对此表示高度满意。
140 0
|
SQL IDE Java
如何高效编码? 使用有意义的命名
编码中随处可见命名。我们给变量、函数、参数、类和包命名;我们jar文件命名。我们命名,命名,不断命名,既然有怎么多命名要做,不妨就做好它。
|
自然语言处理 数据管理
数据标准和数据规范到底有什么区别???
数据标准和数据规范到底有什么区别???
XV-440-10TVB-1-20 规范并创建了通用模块产品理念
XV-440-10TVB-1-20 规范并创建了通用模块产品理念
187 0
XV-440-10TVB-1-20 规范并创建了通用模块产品理念
|
前端开发 JavaScript 开发者
前端工程化中重要概念之检验/格式化/规范化
前端工程化中检验/格式化/规范化的重要性及其工具介绍 在前端开发中,代码的质量非常重要。为了确保代码的质量,开发者需要对代码进行检验、格式化和规范化。下面介绍一些在前端工程化中常用的检验/格式化/规范化工具及其作用:
140 0
|
敏捷开发
你的团队有完成定义标准吗?
大家好,我是阿萨。现在基本上做软件行业的,大家都在搞敏捷开发。如果没有搞敏捷开发,感觉都不好意思给别人说自己是软件开发。那么敏捷里的最佳实践------完成定义标准你们团队有吗?
109 0
老项目改造返回值规范化
已经运行的项目,开始由于赶工期等因素,未做统一的接口返回规范。现在要做规范化,还必须要保留原先的接口,尤其是APP的接口,有的版本会存在一个比较长的时间。因此需要保留两个版本,又不想维护两套代码。
103 0