介绍

Unicode 是世界上大多数计算机的标准字符编码。它确保文本(包括字母、符号、表情符号,甚至控制字符)在不同设备、平台和数字文档中显示一致,无论使用的操作系统或软件是什么。它是互联网和计算机行业的重要组成部分,没有它,互联网将会更加混乱和难以使用。

Unicode 本身不是编码,而更像是地球上几乎所有可能字符的数据库。Unicode 包含一个代码点,用于标识其数据库中的每个字符,其值范围从 0 到 110 万,这意味着它很可能不会很快用尽这些独特的代码点。Unicode 中的每个代码点都表示为 U+n,其中 U+ 表示它是一个 Unicode 代码点,n 是字符的四到六个十六进制数字集。它比 ASCII 更强大,ASCII 只表示 128 个字符。使用 ASCII 在全球范围内交换数字文本是困难的,因为它基于美国英语,不支持重音字符。另一方面,Unicode 包含几乎 15 万个字符,涵盖了地球上每种语言的字符。

随之而来的是对编程语言(如 Python)的要求,以正确处理文本,并使软件能够实现国际化。Python 可以用于各种用途——从电子邮件到服务器再到网络——并且具有一种优雅的处理 Unicode 的方式,即通过采用 Unicode 标准来处理其字符串。

然而,在 Python 中处理 Unicode 可能会令人困惑并导致错误。本教程将介绍如何在 Python 中使用 Unicode 的基础知识,以帮助您避免这些问题。您将使用 Python 解释 Unicode,使用 Python 的规范化函数对数据进行规范化,并处理 Python Unicode 错误。

先决条件

要按照本教程操作,您需要:

  • 本地安装或远程服务器上安装的 Python。如果您尚未设置 Python,可以按照我们的教程《如何安装 Python 3 并设置编程环境》进行设置。选择适合您的 Linux 发行版的版本。
  • 熟悉基本的 Python 编程和 Python 的字符串方法
  • 知道如何使用 Python 交互式控制台

步骤 1 —— 在 Python 中转换 Unicode 代码点

编码是将数据表示为计算机可读形式的过程。有许多编码数据的方法——ASCII、Latin-1 等——每种编码都有其优势和劣势,但也许最常见的是 UTF-8。这是一种编码类型,允许来自世界各地的字符在单个字符集中表示。因此,UTF-8 对于任何处理国际化数据的人来说都是一种必不可少的工具。总的来说,UTF-8 对于大多数目的来说都是一个不错的选择。它相对高效,并且可以与各种软件一起使用。UTF-8 将 Unicode 代码点转换为计算机可以理解的十六进制字节。换句话说,Unicode 是映射,而 UTF-8 使计算机能够理解该映射。

在 Python 3 中,默认的字符串编码是 UTF-8,这意味着 Python 字符串中的任何 Unicode 代码点都会自动转换为相应的字符。

在这一步中,您将使用 Python 中的 Unicode 代码点创建版权符号(©)。首先,在终端中启动 Python 交互式控制台,然后输入以下内容:

>>> s ='\u00A9'>>> s

在上述代码中,您创建了一个带有 Unicode 代码点 \u00A9 的字符串 s。如前所述,由于 Python 字符串默认使用 UTF-8 编码,打印 s 的值会自动将其更改为相应的 Unicode 符号。请注意,代码点前面的 \u 是必需的。没有它,Python 将无法转换代码点。上述代码的输出将返回相应的 Unicode 符号:

'©'

Python 编程语言提供了用于编码和解码字符串的内置函数。encode() 函数将字符串转换为字节字符串。

为了演示这一点,打开 Python 交互式控制台,然后输入以下代码:

>>> '🅥'.encode('utf-8')

这将产生字符的字节字符串作为输出:

b'\xf0\x9f\x85\xa5'

请注意,每个字节前面都有 \x,表示它是一个十六进制数。

接下来,您将使用 decode() 函数将字节字符串转换回字符串。decode() 函数接受编码类型作为参数。值得一提的是,decode() 函数只能解码字节字符串,这是通过在字符串开头使用字母 b 来指定的。去除 b 将导致 AttributeError

在您的控制台中输入:

>>> b'\xf0\x9f\x85\xa5'.decode('utf-8')

该代码将返回如下输出:

'🅥'

现在,您对 Python 中的 Unicode 解释有了基本的了解。接下来,您将深入了解 Python 内置的 unicodedata 模块,以在字符串上执行高级的 Unicode 技术。

步骤 2 —— 在 Python 中对 Unicode 进行规范化

在这一步中,你将学习如何在 Python 中对 Unicode 进行规范化。规范化有助于确定不同字体中的两个字符是否相同,这在两个具有不同代码点但产生相同结果的字符时非常有用。例如,Unicode 字符 Râ„œ 在人眼中是相同的,因为它们都是字母 R,但计算机认为它们是不同的字符。

下面的代码示例进一步说明了这一点。打开你的 Python 控制台,输入以下内容:

>>> styled_R = 'ℜ'>>> normal_R = 'R'>>> styled_R == normal_R

你将得到以下输出:

False

该代码输出 False,因为 Python 字符串不认为这两个字符是相同的。这种区分能力是在处理 Unicode 时规范化很重要的原因。

在 Unicode 中,一些字符是由两个或多个字符组合而成的。在这种情况下,规范化非常重要,因为它可以使你的字符串保持一致。为了更好地理解这一点,打开你的 Python 控制台,输入以下代码:

>>> s1 ='hôtel'>>> s2 = 'ho\u0302tel'>>> len(s1), len(s2)

在上面的代码中,你创建了一个包含 ô 字符的字符串 s1,在第二行,字符串 s2 包含了尖音字符( Ì‚ )。执行后,代码返回以下输出:

(5, 6)

上面的输出显示,这两个字符串由相同的字符组成,但长度不同,这意味着它们不会相等。在同一个控制台中输入以下内容进行测试:

>>> s1 == s2

该代码返回以下输出:

False

尽管字符串变量 s1s2 产生相同的 Unicode 字符,但它们的长度不同,因此它们不相等。

你可以使用 normalize() 函数来解决这个问题,这也是你将在下一步中要做的事情。

步骤 3 —— 使用 NFD、NFC、NFKD 和 NFKC 对 Unicode 进行规范化

在这一步中,你将使用 Python 的 unicodedata 库中的 normalize() 函数来对 Unicode 字符串进行规范化。unicodedata 模块提供了字符查找和规范化功能。normalize() 函数的第一个参数可以是规范化形式,第二个参数是要规范化的字符串。Unicode 有四种规范化形式可供使用:NFD、NFC、NFKD 和 NFKC。

NFD 规范化形式将一个字符分解为多个组合字符。它使你的文本不区分重音,这在搜索和排序时非常有用。你可以通过将字符串编码为字节来实现这一点。

在你的控制台中输入以下内容:

>>> from unicodedata import normalize>>> s1 ='hôtel'>>> s2 = 'ho\u0302tel'>>> s1_nfd = normalize('NFD', s1)>>> len(s1), len(s1_nfd)

该代码产生以下输出:

(5, 6)

正如示例所示,规范化字符串 s1 会使其长度增加一个字符。这是因为 ô 符号被分解为两个字符 oˆ,你可以通过以下代码确认:

>>> s1.encode(), s1_nfd.encode()

执行后的输出显示,在对规范化后的字符串进行编码后,o 字符从字符串 s1_nfd 中分离出来:

(b'h\xc3\xb4tel', b'ho\xcc\x82tel')

NFC 规范化形式首先分解一个字符,然后重新组合为任何可用的组合字符。由于 NFC 会将一个字符串组合成最短可能的输出,W3C 建议在网络上使用 NFC。键盘输入默认返回组合字符串,因此在这种情况下使用 NFC 是个好主意。

作为示例,在你的交互式控制台中输入以下内容:

>>> from unicodedata import normalize>>> s2_nfc = normalize('NFC', s2)>>> len(s2), len(s2_nfc)

该代码产生以下输出:

(6, 5)

在示例中,规范化字符串 s2 的长度减少了一个字符。你可以通过在交互式控制台中运行以下代码来确认这一点:

>>> s2.encode(), s2_nfc.encode()

该代码的输出是:

(b'ho\xcc\x82tel', b'h\xc3\xb4tel')

输出显示,oˆ 字符合并成了单个 ô 字符。

NFKDNFKC 规范化形式用于“严格”规范化,并可用于与 Unicode 字符串相关的各种搜索和模式匹配问题。NFKD 和 NFKC 中的 “K” 代表兼容性。

NFD 和 NFC 规范化形式会分解字符,但 NFKD 和 NFKC 会对不相似但等效的字符执行兼容性分解,去除任何格式上的区别。例如,字符串 â‘¡â‘ 21 不相似,但它们都表示相同的值。NFKC 和 NFKD 规范化形式会去除这些字符中的格式(在本例中是数字周围的圆圈),以提供它们的最简形式。

以下示例演示了 NFD 和 NFKD 规范化形式之间的区别。在你的 Python 交互式控制台中输入以下内容:

>>> s1 = '2�ô'>>> from unicodedata import normalize>>> normalize('NFD', s1), normalize('NFKD', s1)

你将得到以下输出:

('2�ô', '25ô')

输出显示,NFD 形式无法分解字符串 s1 中的指数字符,但 NFKD 去除了指数格式,并用其等效形式(作为数字的 5)替换了兼容字符。请记住,NFD 和 NFKD 规范化形式仍在分解字符,因此你应该期望 ô 字符的长度增加一个,就像你在前面的 NFD 示例中看到的那样。你可以通过运行以下代码来确认这一点:

>>> len(normalize('NFD', s1)), len(normalize('NFKD', s1))

该代码将返回以下内容:

(4, 4)

NFKC 规范化形式的工作方式类似,但是它会组合字符而不是分解字符。在同一个 Python 控制台中输入以下内容:

>>> normalize('NFC', s1), normalize('NFKC', s1)

该代码返回以下内容:

('2�ô', '25ô')

由于 NFKC 遵循组合方法,你应该期望 ô 字符的字符串长度减少一个,而不是分解的情况下增加一个。你可以通过运行以下代码来确认这一点:

>>> len(normalize('NFC', s1)), len(normalize('NFKC', s1))

这将返回以下输出:

(3, 3)

通过执行上述步骤,你将对规范化形式有了工作知识,并了解了它们之间的区别。在下一步中,你将解决 Python 中的 Unicode 错误。

第四步 —— 解决 Python 中的 Unicode 错误

在处理 Python 中的 Unicode 时,可能会出现两种类型的 Unicode 错误,即 UnicodeEncodeError 和 UnicodeDecodeError。虽然这些 Unicode 错误可能令人困惑,但可以加以处理。在本步骤中,您将解决这两种错误。

解决 UnicodeEncodeError

Unicode 编码是将 Unicode 字符串使用特定编码转换为字节的过程。当尝试对包含无法用指定编码表示的字符的字符串进行编码时,就会出现 UnicodeEncodeError。

要创建此错误,您将对包含不属于 ASCII 字符集的字符的字符串进行编码。

打开您的控制台,输入以下内容:

>>> ascii_supported = '\u0041'>>> ascii_supported.encode('ascii')

以下是您的输出:

b'A'

然后输入以下内容:

>>> ascii_unsupported = '\ufb06'>>> ascii_unsupported.encode('utf-8')

您将得到以下输出:

b'\xef\xac\x86'

最后,输入以下内容:

>>> ascii_unsupported.encode('ascii')

然而,当运行此代码时,您将收到以下错误:

Traceback (most recent call last):File "", line 1, in UnicodeEncodeError: 'ascii' codec can't encode character '\ufb06' in position 0: ordinal not in range(128)

ASCII 字符集的字符数量有限,当发现一个不在 ASCII 字符集中的字符时,Python 会抛出错误。由于 ASCII 字符集不识别代码点 \ufb06,Python 返回一个错误消息,指出 ASCII 仅有 128 个字符,而该代码点的对应十进制值不在该范围内。

您可以通过在 encode() 函数中使用 errors 参数来处理 UnicodeEncodeError。errors 参数可以有三个值:ignorereplacexmlcharrefreplace

打开您的控制台,输入以下内容:

>>> ascii_unsupported = '\ufb06'>>> ascii_unsupported.encode('ascii', errors='ignore')

您将得到以下输出:

b''

接下来输入以下内容:

>>> >>> ascii_unsupported.encode('ascii', errors='replace')

输出将是:

b'?'

最后,输入以下内容:

>>> ascii_unsupported.encode('ascii', errors='xmlcharrefreplace')

输出为:

b''

在每种情况下,Python 都不会抛出错误。

如前面的示例所示,ignore 会跳过无法编码的字符,replace 会用 ? 替换字符,xmlcharrefreplace 会用 XML 实体替换无法编码的字符。

解决 UnicodeDecodeError

当尝试将包含无法用指定编码表示的字符的字符串解码时,就会出现 UnicodeDecodeError。

要创建此错误,您将尝试将字节字符串解码为无法解码的编码。

打开您的控制台,输入以下内容:

>>> iso_supported = '§'>>> b = iso_supported.encode('iso8859_1')>>> b.decode('utf-8')

您将收到以下错误:

Traceback (most recent call last):File "", line 1, in UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa7 in position 0: invalid start byte

如果遇到此错误,您可以在 decode() 函数中使用 errors 参数,这可以帮助您解码字符串。errors 参数接受两个值,即 ignorereplace

为了演示这一点,打开您的 Python 控制台,输入以下代码:

>>> iso_supported = '§A'>>> b = iso_supported.encode('iso8859_1')>>> b.decode('utf-8', errors='replace')

您将得到以下输出:

'�A'

然后输入以下内容:

>>> b.decode('utf-8', errors='ignore')

您将得到以下输出:

'A'

在上述示例中,使用 decode() 函数中的 replace 值添加了一个 � 字符,而使用 ignore 在解码器(在本例中为 utf-8)无法解码字节时返回空字符串。

在解码任何字符串时,请注意您不能假设编码是什么。要解码任何字符串,您必须知道它是如何编码的。

结论

本文介绍了如何在 Python 中使用 Unicode 的基础知识。您对字符串进行了编码和解码,使用 NFD、NFC、NFKD 和 NFKC 对数据进行了规范化,并解决了 Unicode 错误。您还在涉及排序和搜索的场景中使用了规范化形式。这些技术将帮助您使用 Python 处理 Unicode 问题。