108

使用 Python 2.7,我想知道使用 typeunicode而不是有什么真正的优势str,因为它们似乎都能够保存 Unicode 字符串。除了能够unicode使用转义字符在字符串中设置 Unicode 代码之外,还有什么特殊原因\吗?:

执行一个模块:

# -*- coding: utf-8 -*-

a = 'á'
ua = u'á'
print a, ua

结果:á,á

编辑:

使用 Python shell 进行更多测试:

>>> a = 'á'
>>> a
'\xc3\xa1'
>>> ua = u'á'
>>> ua
u'\xe1'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> ua
u'\xe1'

因此,unicode字符串似乎是使用latin1而不是编码的utf-8,而原始字符串是使用编码的utf-8?我现在更迷茫了!:S

4

4 回答 4

184

unicode旨在处理文本。文本是可能大于单个字节的代码序列。文本可以以特定编码进行编码,以将文本表示为原始字节(例如 ... )。utf-8latin-1

请注意,unicode 未编码!python使用的内部表示是一个实现细节,你不应该关心它,只要它能够表示你想要的代码点。

相反str,在 Python 2 中是一个普通的bytes序列。它不代表文字!

您可以将其unicode视为某些文本的一般表示,可以以多种不同的方式将其编码为通过 表示的二进制数据序列str

注意:在 Python 3 中,unicode已重命名为,str并且有一个新bytes类型用于纯字节序列。

您可以看到的一些差异:

>>> len(u'à')  # a single code point
1
>>> len('à')   # by default utf-8 -> takes two bytes
2
>>> len(u'à'.encode('utf-8'))
2
>>> len(u'à'.encode('latin1'))  # in latin1 it takes one byte
1
>>> print u'à'.encode('utf-8')  # terminal encoding is utf-8
à
>>> print u'à'.encode('latin1') # it cannot understand the latin1 byte
�

请注意,使用str您可以对特定编码表示的单个字节进行较低级别的控制,而使用unicode您只能在代码点级别进行控制。例如,您可以这样做:

>>> 'àèìòù'
'\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9'
>>> print 'àèìòù'.replace('\xa8', '')
à�ìòù

以前是有效的 UTF-8,现在不再是。使用 unicode 字符串,您不能以结果字符串不是有效的 unicode 文本的方式进行操作。您可以删除代码点,用不同的代码点替换代码点等,但您不能弄乱内部表示。

于 2013-08-03T15:32:53.653 回答
51

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,以便于参考。

编码

将字符映射到位模式。

这些位模式用于表示计算机内存或磁盘上的字符。

有许多不同的编码涵盖不同的字符子集。在英语世界中,最常见的编码如下:

ASCII

将 128 个字符(代码点 U+0000 到 U+007F)映射到长度为 7 的位模式。

例子:

  • a → 1100001 (0x61)

您可以在此表中查看所有映射。

ISO 8859-1(又名拉丁-1)

将 191 个字符(代码点 U+0020 到 U+ 007E和 U+00A0 到 U+00FF)映射到长度为 8 的位模式。

例子:

  • a → 01100001 (0x61)
  • á → 11100001 (0xE1)

您可以在此表中查看所有映射。

UTF-8

将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'

uaUnicode 对象和 Latin-1 编码之间没有关系。á 的代码点是 U+00E1,而 á 的 Latin-1 编码是 0xE1(如果您将编码的位模式解释为二进制数)纯属巧合。

于 2018-03-06T19:56:00.817 回答
32

您的终端恰好配置为 UTF-8。

印刷工作的事实a是巧合;您正在将原始 UTF-8 字节写入终端。a是长度为 2 的值包含两个字节,十六进制值 C3 和 A1,而ua是长度为1的 unicode 值,包含代码点 U+00E1。

这种长度差异是使用 Unicode 值的一个主要原因。您无法轻松测量字节字符串中的文本字符数;字节字符串告诉您使用了len()多少字节,而不是编码了多少字符。

当您将 unicode 值编码为不同的输出编码,您可以看到差异:

>>> a = 'á'
>>> ua = u'á'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> a
'\xc3\xa1'

请注意,Unicode 标准的前 256 个代码点与拉丁 1 标准匹配,因此 U+00E1 代码点被编码为拉丁 1,作为具有十六进制值 E1 的字节。

此外,Python 在 unicode 和字节字符串的表示中使用转义码,并且不可打印 ASCII 的低代码点也使用\x..转义值表示。这就是为什么代码点在 128 到 255 之间的 Unicode 字符串看起来就像拉丁 1 编码的原因。如果您有一个代码点超过 U+00FF 的 unicode 字符串,则使用不同的转义序列,\u....并使用四位十六进制值。

看起来您还没有完全理解 Unicode 和编码之间的区别。请在继续之前阅读以下文章:

于 2013-08-03T15:16:25.257 回答
2

当您将 a 定义为 unicode 时,字符 a 和 á 相等。否则 á 算作两个字符。试试 len(a) 和 len(au)。除此之外,您在使用其他环境时可能需要进行编码。例如,如果你使用 md5,你会得到不同的 a 和 ua 值

于 2013-08-03T15:19:22.767 回答