Unicode 和编码是完全不同的、不相关的东西。
统一码
为每个字符分配一个数字 ID:
- 0x41 → 一个
- 0xE1 → á
- 0x414 → Д
因此,Unicode 将数字 0x41 分配给 A,将 0xE1 分配给 á,将 0x414 分配给 Д。
甚至我使用的小箭头 → 也有它的 Unicode 编号,它是 0x2192。甚至表情符号也有其 Unicode 编号,即 0x1F602。
您可以在此表中查找所有字符的 Unicode 编号。特别是,您可以在此处找到上面的前三个字符,在此处找到箭头,在此处找到表情符号。
这些由 Unicode 分配给所有字符的数字称为代码点。
所有这些的目的是提供一种明确引用每个字符的方法。例如,如果我说的是Unicode 代码点 0x1F602 ,而不是说“你知道,这个笑着流泪的表情符号”。更容易,对吧?
请注意,Unicode 代码点通常使用前导 进行格式化U+
,然后将十六进制数值填充到至少 4 位。所以,上面的例子是 U+0041、U+00E1、U+0414、U+2192、U+1F602。
Unicode 代码点的范围从 U+0000 到 U+10FFFF。那是 1,114,112 个数字。这些数字中有 2048 个用于代理,因此,还有 1,112,064 个。这意味着,Unicode 可以为 1,112,064 个不同的字符分配唯一的 ID(代码点)。并非所有这些代码点都分配给一个字符,并且 Unicode 不断扩展(例如,当引入新的表情符号时)。
需要记住的重要一点是,Unicode 所做的只是为每个字符分配一个称为代码点的数字 ID,以便于参考。
编码
将字符映射到位模式。
这些位模式用于表示计算机内存或磁盘上的字符。
有许多不同的编码涵盖不同的字符子集。在英语世界中,最常见的编码如下:
将 128 个字符(代码点 U+0000 到 U+007F)映射到长度为 7 的位模式。
例子:
您可以在此表中查看所有映射。
将 191 个字符(代码点 U+0020 到 U+ 007E和 U+00A0 到 U+00FF)映射到长度为 8 的位模式。
例子:
- a → 01100001 (0x61)
- á → 11100001 (0xE1)
您可以在此表中查看所有映射。
将1,112,064个字符(所有现有的 Unicode 代码点)映射到长度为 8、16、24 或 32 位(即 1、2、3 或 4 字节)的位模式。
例子:
- a → 01100001 (0x61)
- á → 11000011 10100001 (0xC3 0xA1)
- ≠ → 11100010 10001001 10100000 (0xE2 0x89 0xA0)
- → 11110000 10011111 10011000 10000010 (0xF0 0x9F 0x98 0x82)
UTF-8 将字符编码为位串的方式在此处进行了很好的描述。
Unicode 和编码
看看上面的例子,Unicode 的用处就很清楚了。
例如,如果我是Latin-1并且我想解释我的 á 编码,我不需要说:
“我用 aigu 将 a 编码为 11100001”
但我只能说:
“我将 U+00E1 编码为 11100001”
如果我是UTF-8,我可以说:
“我,反过来,我将 U+00E1 编码为 11000011 10100001”
每个人都非常清楚我们指的是哪个角色。
现在到经常出现的混乱
确实,有时编码的位模式(如果将其解释为二进制数)与该字符的 Unicode 代码点相同。
例如:
- ASCII 将 a 编码为1100001,您可以将其解释为十六进制数字0x61 ,而a的 Unicode 代码点是U+0061。
- Latin-1 将á编码为 11100001,您可以将其解释为十六进制数字0xE1 ,而á的 Unicode 代码点是U+00E1。
当然,为了方便,特意这样安排的。但你应该把它看作纯属巧合。用于表示内存中字符的位模式与该字符的 Unicode 代码点没有任何关系。
甚至没有人说您必须将像 11100001 这样的位字符串解释为二进制数。只需将其视为 Latin-1 用于编码字符á的位序列。
回到你的问题
Python 解释器使用的编码是UTF-8。
这是您的示例中发生的事情:
示例 1
下面以 UTF-8 对字符 á 进行编码。这导致位串 11000011 10100001 保存在变量 中a
。
>>> a = 'á'
当您查看 的值时a
,其内容 11000011 10100001 被格式化为十六进制数 0xC3 0xA1 并输出为'\xc3\xa1'
:
>>> a
'\xc3\xa1'
示例 2
下面将á的Unicode码点,即U+00E1保存在变量中ua
(我们不知道Python内部使用哪种数据格式来表示内存中的码点U+00E1,对我们来说并不重要):
>>> ua = u'á'
当您查看 的值时ua
,Python 会告诉您它包含代码点 U+00E1:
>>> ua
u'\xe1'
示例 3
下面使用 UTF-8 对 Unicode 代码点 U+00E1(表示字符 á)进行编码,从而产生位模式 11000011 10100001。同样,对于输出,此位模式表示为十六进制数 0xC3 0xA1:
>>> ua.encode('utf-8')
'\xc3\xa1'
示例 4
下面用 Latin-1 对 Unicode 代码点 U+00E1(表示字符 á)进行编码,得到位模式 11100001。对于输出,该位模式表示为十六进制数 0xE1,巧合的是,它与初始的相同代码点 U+00E1:
>>> ua.encode('latin1')
'\xe1'
ua
Unicode 对象和 Latin-1 编码之间没有关系。á 的代码点是 U+00E1,而 á 的 Latin-1 编码是 0xE1(如果您将编码的位模式解释为二进制数)纯属巧合。