Python str与unicode类型


101

使用Python 2.7,我想知道使用type 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可以通过str某种编码(例如utf-8)转换为。

Answers:


178

unicode用于处理文本。文本是一个代码点序列,可能大于一个字节。文本可以被编码在一个特定的编码来表示文本作为原始字节(例如utf-8latin-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

请注意,使用时,str您可以控制特定编码表示形式的单个字节,而使用时unicode,则只能在代码点级别进行控制。例如,您可以执行以下操作:

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

以前是有效的UTF-8,现在已经不复存在了。使用unicode字符串,您不能以结果字符串不是有效的unicode文本的方式进行操作。您可以删除代码点,将代码点替换为其他代码点等,但不能与内部表示混淆。


4
非常感谢您的回答,对您有所帮助!对我来说,最清楚的部分是:“ unicode未编码!python使用的内部表示形式是实现细节,因此您不必关心它。[...]”。因此,当unicode我序列化对象时,我想我们首先必须将encode()它们显式地指定为正确的编码格式,因为我们不知道内部使用哪个对象来表示该unicode值。
Caumons 2013年

10
是。当您想保存一些文本(例如保存到文件中)时,必须用字节表示,即您必须对其进行编码。检索内容时,您应该知道所使用的编码,以便能够将字节解码unicode对象。
巴库里

抱歉,unicode未编码的语句是完全错误的。UTF-16 / UCS-2和UTF-32 / UCS-4也是编码...将来可能会创建更多此类编码。重点是,仅仅因为您不关心实现细节(并且实际上,您不应该!),仍然并不意味着它unicode没有被编码。当然可以。是否可以.decode()是一个完全不同的故事。
0xC0000022L

1
@ 0xC0000022L句子不清楚,可能是句子。应该说:unicode对象的内部表示可以是任何想要的,包括非标准的表示。特别是在python3 +中,unicode 确实使用了非标准的内部表示形式,该表示形式也会根据所包含的数据而变化。因此,这不是标准编码。Unicode作为文本标准仅定义了代码点,这些代码点是文本的抽象表示形式,在内存中有很多编码unicode的方法,包括标准utf-X等。Python使用自己的方法来提高效率。
Bakuriu

1
@ 0xC0000022L同样,UTF-16是一种编码的事实与CPython的对象无关unicode,因为它既不使用UTF-16,也不使用UTF-32。它使用临时表示,如果要将数据编码为实际字节,则必须使用encode。另外:语言不要求如何unicode实现,因此python的不同版本或实现可以(并且确实)具有不同的内部表示形式。
Bakuriu

38

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(又名Latin-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(或您将其称为上升条)编码为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 = 'á'

当您查看的值时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(表示字符á)进行编码,结果得到位模式1100001110100001。同样,对于输出,该位模式也表示为十六进制数字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(如果将编码的位模式解释为二进制数)纯属巧合。


31

您的终端碰巧已配置为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'

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

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

看来您尚未完全了解Unicode和编码之间的区别。在继续之前,请务必阅读以下文章:


我已经通过进一步测试编辑了我的问题。我读unicode和不同的编码已有一段时间了,我想我理解了这一理论,但是当实际测试Python代码时,我不知道发生了什么
Caumons

1
latin-1编码与Unicode标准的前256个代码点匹配。这就是U + 00E1编码为\xe1拉丁文1的原因
。–马丁·彼得斯

2
这是Unicode最重要的一个方面。它不是编码。是文字。Unicode是一个包含大量内容的标准,例如有关哪些代码点是数字,空格或其他类别的信息,应从左到右或从右到左等显示等等。等等
马丁·皮特斯

1
这就像说Unicode就像是一个“接口”,而编码是像实际的“实现”一样。
Caumons 2013年

2
@Varun:您必须使用Python 2狭窄版本,该版本内部使用UCS-2并将U + FFFF上的任何内容误表示为长度为2。Python 3中和UCS-2(宽)版本将显示你的长度确实是1
的Martijn Pieters的

2

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.