Python str vs unicode类型

Cau*_*ons 98 python string unicode

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

执行模块:

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

a = 'á'
ua = u'á'
print a, ua
Run Code Online (Sandbox Code Playgroud)

结果:á,á

编辑:

使用Python shell进行更多测试:

>>> a = 'á'
>>> a
'\xc3\xa1'
>>> ua = u'á'
>>> ua
u'\xe1'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> ua
u'\xe1'
Run Code Online (Sandbox Code Playgroud)

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

Bak*_*riu 173

unicode是为了处理文本.文本是一系列代码点,可能比单个字节大.文本可以被编码在一个特定的编码来表示文本作为原始字节(例如utf-8,latin-1...).

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

相反,str在Python 2中是一个简单的字节序列.它不代表文字!

您可以将其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
?
Run Code Online (Sandbox Code Playgroud)

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

>>> 'àèìòù'
'\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9'
>>> print 'àèìòù'.replace('\xa8', '')
à?ìòù
Run Code Online (Sandbox Code Playgroud)

之前有效的UTF-8已经不存在了.使用unicode字符串时,您无法以生成的字符串不是有效的unicode文本的方式操作.您可以删除代码点,使用不同的代码点替换代码点等,但不能弄乱内部表示.

  • 是.当你想保存一些文本(例如文件)时,你必须用字节来表示它,即你必须*编码*它.检索内容时,您应该知道所使用的编码,以便能够将字节*解码为`unicode`对象. (10认同)
  • 非常感谢您的回答,它帮助了很多!对我来说最明确的部分是:"unicode没有编码!python使用的内部表示是一个实现细节,你不应该关心它[...]".因此,在序列化`unicode`对象时,我想我们首先必须明确地`编码()`它们到正确的编码格式,因为我们不知道在内部使用哪一个来表示`unicode`值. (4认同)
  • @0xC0000022L 也许这句话不清楚。应该说:“unicode”对象内部表示可以是任何它想要的,包括非标准的。特别是在 python3+ 中,‘unicode’ **确实**使用非标准内部表示,该表示也根据所包含的数据而变化。因此它不是*标准编码*。Unicode 作为文本标准仅定义了“代码点”,它是文本的“抽象表示”,有很多方法可以在内存中对 unicode 进行编码,包括标准 utf-X 等。Python 使用自己的方式来提高效率。 (2认同)
  • @0xC0000022L 另外,UTF-16 是一种编码这一事实与 CPython 的“unicode”对象没有任何关系,因为它“不”使用 UTF-16,也不使用 UTF-32。它使用临时表示,如果您想将数据编码为实际字节,则必须使用“encode”。另外:该语言不强制要求“unicode”是如何实现的,因此不同版本或Python的实现可以(并且*确实有*)不同的内部表示。 (2认同)

Mar*_*ers 30

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

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

这种长度差异是使用Unicode值的一个主要原因; 你不能轻易测量字节字符串中的文本字符数; 该len()字节串的告诉你有多少字节被使用,许多字符没有编码方式.

你可以看到差异,当你编码的Unicode值到不同的输出编码:

>>> a = 'á'
>>> ua = u'á'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> a
'\xc3\xa1'
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • latin-1 编码与 Unicode 标准的前 256 个代码点匹配。这就是 U+00E1 在拉丁语 1 中编码为 `\xe1` 的原因。 (2认同)
  • 这是Unicode最重要的一个方面.*它不是编码*.这是文字.Unicode是一种标准,包括更多,如关于哪些代码点是数字,或者空格或其他类别的信息,应该从左到右或从右到左等显示等.等等. (2认同)
  • @Varun:你必须使用Python 2的窄版本,它在内部使用UCS-2并且将U + FFFF上的任何内容误传为长度为2.Python 3和UCS-2(宽)版本将显示长度为1. (2认同)

wei*_*eld 29

Unicode和编码是完全不同的,无关的东西.

统一

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

  • 0x41→A
  • 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编码一个为1100001,您可以解释为十六进制数0x61,和的Unicode代码点一个U + 0061.
  • Latin-1将á编码为11100001,您可以将其解释为十六进制数0xE1,并且á的Unicode代码点为U + 00E1.

当然,为了方便起见,这是为了这样安排的.但你应该把它看作纯粹的巧合.用于表示内存中字符的位模式不以任何方式与此字符的Unicode代码点相关联.

甚至没有人说你必须将像11100001这样的字符串解释为二进制数.只需将其视为Latin-1用于编码字符á的位序列.

回到你的问题

Python解释器使用的编码是UTF-8.

以下是您的示例中发生的情况:

例1

以下编码UTF-8中的字符á.这导致位串11000011 10100001,其保存在变量中a.

>>> a = 'á'
Run Code Online (Sandbox Code Playgroud)

当您查看其值时a,其内容11000011 10100001被格式化为十六进制数0xC3 0xA1并输出为'\xc3\xa1':

>>> a
'\xc3\xa1'
Run Code Online (Sandbox Code Playgroud)

例2

以下保存变量中的Unicode代码点á,即U + 00E1 ua(我们不知道Python内部使用哪种数据格式来表示内存中的代码点U + 00E1,这对我们来说并不重要):

>>> ua = u'á'
Run Code Online (Sandbox Code Playgroud)

当你看到它的值时ua,Python告诉你它包含代码点U + 00E1:

>>> ua
u'\xe1'
Run Code Online (Sandbox Code Playgroud)

例3

下面使用UTF-8对Unicode代码点U + 00E1(表示字符á)进行编码,这会产生位模式11000011 10100001.同样,对于输出,此位模式表示为十六进制数0xC3 0xA1:

>>> ua.encode('utf-8')
'\xc3\xa1'
Run Code Online (Sandbox Code Playgroud)

例4

下面使用Latin-1对Unicode代码点U + 00E1(表示字符á)进行编码,得到位模式11100001.对于输出,此位模式表示为十六进制数0xE1,其重合与初始相同代码点U + 00E1:

>>> ua.encode('latin1')
'\xe1'
Run Code Online (Sandbox Code Playgroud)

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