【问题标题】:Python str vs unicode typesPython str 与 unicode 类型
【发布时间】:2013-08-04 17:59:55
【问题描述】:

使用 Python 2.7,我想知道使用类型 unicode 而不是 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

【问题讨论】:

  • unicode 没有编码,它只是 unicode 字符的抽象; unicode 可以通过一些编码(例如 utf-8 )转换为 str
  • shell 的默认编码是latin1(您可以在 shell 设置中更改)。在您的模块中,您告诉解释器和编译器所有编码都是utf-8。因此输出的差异

标签: python string unicode


【解决方案1】:

unicode 用于处理文本。文本是一系列代码点可能大于单个字节。文本可以以特定编码编码以将文本表示为原始字节(例如utf-8latin-1...)。

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

相反,Python 2 中的 strbytes 的普通序列。不代表文字!

您可以将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 文本的方式进行操作。 您可以删除代码点,用不同的代码点替换代码点等,但不能弄乱内部表示。

【讨论】:

  • 非常感谢您的回答,帮助很大!对我来说最清楚的部分是:“unicode 没有编码!python 使用的内部表示是一个实现细节,你不应该关心它 [...]”。所以,在序列化unicode 对象时,我想我们首先必须将encode() 明确地转换为正确的编码格式,因为我们不知道内部使用哪个来表示unicode 值。
  • 是的。当你想保存一些文本(例如到一个文件)时,你必须用字节来表示它,即你必须 encode 它。检索内容时,您应该知道所使用的编码,以便能够将字节解码unicode 对象。
  • 很抱歉,unicode 未编码的说法是完全错误的。 UTF-16/UCS-2 和 UTF-32/UCS-4 也是编码……未来可能会创建更多这样的编码。重点是,仅仅因为您不应该关心实现细节(事实上,您不应该!),仍然不意味着 unicode 没有被编码。当然是。是否可以.decode()'d 是完全不同的故事。
  • @0xC0000022L 也许这句话不清楚。它应该说:unicode 对象的内部表示可以是任何它想要的,包括非标准的。特别是在 python3+ unicode 确实 使用非标准的内部表示,该表示也会根据包含的数据而变化。因此,它不是标准编码。 Unicode 作为文本标准只定义了 codepoints,它们是文本的抽象表示,有很多方法可以在内存中对 unicode 进行编码,包括标准的 utf-X 等。Python 使用以自己的方式提高效率。
  • @0xC0000022L 此外,UTF-16 是一种编码这一事实与 CPython 的 unicode 对象无关,因为它使用UTF-16,也不是 UTF-32。它使用特殊表示,如果您想将数据编码为实际字节,您必须使用encode。另外:该语言没有规定如何实现unicode,因此不同版本或python的实现可以(并且确实有)不同的内部表示。
【解决方案2】:

Unicode 和编码是完全不同的、不相关的东西。

Unicode

为每个字符分配一个数字 ID:

  • 0x41 → 一个
  • 0xE1 → á
  • 0x414 → Д

因此,Unicode 将数字 0x41 分配给 A,将 0xE1 分配给 á,并将 0x414 分配给 Д。

即使是我使用的小箭头 → 也有它的 Unicode 编号,它是 0x2192。甚至表情符号都有它们的 Unicode 数字,? 是 0x1F602。

您可以在this table 中查找所有字符的Unicode 编号。尤其是here上方的前三个字符、箭头here和表情符号here

这些由 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 个用于surrogates,因此还剩下 1,112,064 个。这意味着,Unicode 可以为 1,112,064 个不同的字符分配唯一的 ID(代码点)。尚未将所有这些代码点都分配给一个字符,并且 Unicode 会不断扩展(例如,当引入新的表情符号时)。

要记住的重要一点是,Unicode 所做的只是为每个字符分配一个称为代码点的数字 ID,以便于参考。

编码

将字符映射到位模式。

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

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

ASCII

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

例子:

  • a → 1100001 (0x61)

你可以在这个table看到所有的映射。

ISO 8859-1 (aka Latin-1)

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

例子:

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

你可以在这个table看到所有的映射。

UTF-8

1,112,064 characters(所有现有的 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 将字符编码为位串的方式已经很好地描述了here

Unicode 和编码

看看上面的例子,Unicode的用处就很清楚了。

例如,如果我是 Latin-1 并且我想解释我的 á 编码,我不需要说:

“我用 aigu 将 a 编码为 11100001”

但我只能说:

“我将 U+00E1 编码为 11100001”

如果我是 UTF-8,我可以说:

“我反过来将 U+00E1 编码为 11000011 10100001”

每个人都清楚我们指的是哪个角色。

现在到经常出现的困惑

确实,有时编码的位模式,如果您将其解释为二进制数,则与该字符的 Unicode 代码点相同。

例如:

  • ASCII将a编码为1100001,可以理解为16进制数0x61a的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'

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

【讨论】:

    【解决方案3】:

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

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

    这种长度差异是使用 Unicode 值的主要原因之一;您不能轻易测量字节字符串中 text 字符的数量;字节字符串的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 编码。如果您的 unicode 字符串的代码点超过 U+00FF,则使用不同的转义序列,则使用 \u....,并使用四位十六进制值。

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

    【讨论】:

    • 我已经通过进一步测试编辑了我的问题。我已经阅读了一段时间的 unicode 和不同的编码,我想我理解了这个理论,但是在实际测试 Python 代码时,我不知道发生了什么
    • latin-1 编码匹配 Unicode 标准的前 256 个代码点。这就是 U+00E1 在拉丁语 1 中编码为 \xe1 的原因。
    • 这是 Unicode 最重要的一个方面。 这不是编码。它是文本。 Unicode 是一个包含更多内容的标准,例如关于哪些代码点是数字、空格或其他类别的信息,应该从左到右或从右到左显示等等等等。
    • 这就像说 Unicode 就像一个“接口”,而编码就像一个实际的“实现”。
    • @Varun:您必须使用 Python 2 窄版本,该版本在内部使用 UCS-2,并将 U+FFFF 上的任何内容歪曲为长度为 2。 Python 3 和 UCS-2(宽)构建将显示长度实际上是 1。
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2013-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-15
      • 2022-07-03
      • 1970-01-01
      相关资源
      最近更新 更多