什么是字符串和字节字符串之间的区别?

385

我正在使用一个返回“字节字符串”(bytes)的库,我需要将其转换为字符串。

实际上这两者有什么区别吗?它们之间有什么关系,我该如何进行转换?


9个回答

729
计算机能够存储的唯一内容是字节。
要在计算机中存储任何东西,你必须首先对其进行编码,即将其转换为字节。例如:
- 如果你想要存储音乐,你必须首先使用 MP3WAV 等格式进行编码。 - 如果你想要存储图片,你必须首先使用 PNGJPEG 等格式进行编码。 - 如果你想要存储文本,你必须首先使用 ASCIIUTF-8 等格式进行编码。
MP3、WAV、PNG、JPEG、ASCII和UTF-8是编码的示例。编码是一种以字节表示音频、图像、文本等格式的方法。
在Python中,字节字符串就是一个字节序列。它不可读。在底层,所有东西都必须转换为字节字符串才能存储在计算机中。
另一方面,字符字符串,通常称为“字符串”,是一系列字符。它是可读的。字符字符串不能直接存储在计算机中,它必须首先进行编码(转换为字节字符串)。有多种编码可以将字符字符串转换为字节字符串,例如ASCII和UTF-8。
'I am a string'.encode('ASCII')

上述Python代码将使用ASCII编码对字符串'I am a string'进行编码。上述代码的结果将是一个字节字符串。如果您打印它,Python将表示它为b'I am a string'。但请记住,字节字符串不可读,只是当您打印它们时,Python会从ASCII解码它们。在Python中,字节字符串由一个b和字节字符串的ASCII表示组成。
如果您知道用于编码的编码方式,可以将字节字符串解码回字符字符串。
b'I am a string'.decode('ASCII')

上述代码将返回原始字符串'I am a string'
编码和解码是互为反操作的。所有内容在写入磁盘之前都必须进行编码,而在人类阅读之前必须进行解码。

114
Zenadix值得赞赏。在这个环境中工作了几年后,他的解释是第一个让我明白的。我可能会将它纹在我的另一只手臂上(我的一只手臂已经刻有 Joel Spolsky 的《每位软件开发者绝对必须知道的Unicode和字符集的绝对最低限度(无任何借口!)》)。 - neil.millikin
7
非常出色。清晰易懂。但是,我想提一下这句话 -“如果你打印它,Python会把它表示为'b'I am a string''”,对于Python3和Python2来说,bytes和str是相同的东西。 - SRC
12
我授予您此悬赏,因为您提供了一个非常通俗易懂的解释,使这个主题更加清晰易懂! - fedorqui
5
好的回答。唯一可能需要补充的是更清晰地指出,历史上,程序员和编程语言往往明示或暗示字节序列和ASCII字符串是相同的东西。 Python 3决定明确打破这种假设,在我看来是正确的。 - nekomatic
6
以下是尼尔·米利金提到的乔尔的帖子链接:https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/。 - Kshitij Saraogi
显示剩余11条评论

362
假设使用Python 3(在Python 2中,这种差异定义得不太清楚)- 字符串是字符序列,即unicode codepoints;这些是抽象概念,不能直接存储在磁盘上。字节字符串是一系列字节 - 可以存储在磁盘上的东西。它们之间的映射是一个编码 - 这里有相当多的编码(并且可以无限制地进行扩展) - 您需要知道在特定情况下适用的编码才能进行转换,因为不同的编码可能会将相同的字节映射到不同的字符串中:
>>> b'\xcf\x84o\xcf\x81\xce\xbdo\xcf\x82'.decode('utf-16')
'蓏콯캁澽苏'
>>> b'\xcf\x84o\xcf\x81\xce\xbdo\xcf\x82'.decode('utf-8')
'τoρνoς'

一旦你知道该使用哪个,就可以使用字节字符串的.decode()方法来获取正确的字符字符串,如上所示。为了完整起见,字符字符串的.encode()方法则相反。
>>> 'τoρνoς'.encode('utf-8')
b'\xcf\x84o\xcf\x81\xce\xbdo\xcf\x82'

9
澄清一下给Python 2用户:str类型与bytes类型是相同的;这个回答等价于将unicode类型(在Python 3中不存在)与str类型进行比较。 - craymichael
4
@KshitijSaraogi,这也不完全正确;整个句子被编辑过,有点令人遗憾。Python 3的str对象的内存表示对于Python方面来说既不可访问也不相关;该数据结构只是一系列码位的序列。根据PEP 393,确切的内部编码是Latin-1、UCS2或UCS4之一,并且utf-8表示可以在首次请求后被缓存,但是即使是C代码也不鼓励依赖这些内部细节。 - lvc
3
如果它们无法直接存储在磁盘上,那么它们是如何存储在内存中的? - z33k
2
@orety,出于这个原因,它们确实必须以某种方式进行内部编码,但是这并不像浮点数存储一样需要通过Python代码向您公开。 - lvc
4
请参见上面的评论 - 当然它们以某种方式存储在内存中,但是该形式已被明确抽象化。实际上,现在它可以在程序生命周期内更改,并且在不同的字符串之间可能不同,甚至可能不止一个(某些编码被缓存),这取决于其中的字符 - 但唯一需要担心的时候是如果您正在攻击字符串类型本身的实现。 - lvc
显示剩余7条评论

34

注意:由于Python 2的生命周期即将结束,因此我将更详细地解释Python 3的答案。

在Python 3中

bytes由8位无符号值序列组成,而str由Unicode代码点序列组成,表示人类语言的文本字符。

>>> # bytes
>>> b = b'h\x65llo'
>>> type(b)
<class 'bytes'>
>>> list(b)
[104, 101, 108, 108, 111]
>>> print(b)
b'hello'
>>>
>>> # str
>>> s = 'nai\u0308ve'
>>> type(s)
<class 'str'>
>>> list(s)
['n', 'a', 'i', '̈', 'v', 'e']
>>> print(s)
naïve

尽管 `bytes` 和 `str` 看起来很相似,但它们的实例彼此不兼容,即 `bytes` 和 `str` 实例不能与诸如 `>` 和 `+` 的运算符一起使用。此外,请注意,比较 `bytes` 和 `str` 实例是否相等,即使用 `==`,即使它们包含完全相同的字符,结果也始终为 `False`。
>>> # concatenation
>>> b'hi' + b'bye' # this is possible
b'hibye'
>>> 'hi' + 'bye' # this is also possible
'hibye'
>>> b'hi' + 'bye' # this will fail
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't concat str to bytes
>>> 'hi' + b'bye' # this will also fail
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "bytes") to str
>>>
>>> # comparison
>>> b'red' > b'blue' # this is possible
True
>>> 'red'> 'blue' # this is also possible
True
>>> b'red' > 'blue' # you can't compare bytes with str
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '>' not supported between instances of 'bytes' and 'str'
>>> 'red' > b'blue' # you can't compare str with bytes
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '>' not supported between instances of 'str' and 'bytes'
>>> b'blue' == 'red' # equality between str and bytes always evaluates to False
False
>>> b'blue' == 'blue' # equality between str and bytes always evaluates to False
False

处理 bytesstr 的另一个问题涉及使用内置函数 open 返回的文件。一方面,如果您想要读取或写入二进制数据到/从文件中,总是使用二进制模式打开文件,例如 'rb' 或 'wb'。另一方面,如果您想要读取或写入 Unicode 数据到/从文件中,请注意您计算机的默认编码,因此如有必要,请传递 encoding 参数以避免意外。 在 Python 2 中 str 由 8 位值序列组成,而 unicode 由 Unicode 字符序列组成。需要记住的一件事是,如果 str 只包含 7 位 ASCI 字符,则可以将 strunicode 与运算符一起使用。
在 Python 2 中,使用辅助函数在 strunicode 之间进行转换可能会很有用,在 Python 3 中则是在 bytesstr 之间进行转换。

7

我们来看一个单字符字符串'š',将其编码为一系列字节:

>>> 'š'.encode('utf-8')
b'\xc5\xa1'

为了举例说明,让我们将字节序列以二进制形式显示:

>>> bin(int(b'\xc5\xa1'.hex(), 16))
'0b1100010110100001'

现在一般情况下,如果不知道信息是如何编码的,就无法将其解码回原始字符串。只有当你知道使用的是UTF-8文本编码时,才能按照UTF-8解码算法来还原原始字符串:

11000101 10100001
   ^^^^^   ^^^^^^
   00101   100001

你可以将二进制数101100001显示为字符串:
>>> chr(int('101100001', 2))
'š'

关于“将其编码为一系列字节”:但在被编码之前,它必须具有某种表示形式。那个表示形式是什么?ISO 8859-1 - Peter Mortensen

6

来自什么是Unicode?

基本上,计算机只处理数字。它们通过为每个字母和其他字符分配一个数字来存储这些字符。

......

Unicode为每个字符提供了唯一的数字,无论平台、程序或语言如何。

因此,当计算机表示字符串时,它通过它们独特的Unicode编号在计算机中找到字符串中的字符,并将这些数字存储在内存中。但是,您不能直接通过它们的独特Unicode编号将字符串写入磁盘或通过网络传输字符串,因为这些数字只是简单的十进制数。您应该将字符串编码为字节字符串,例如UTF-8。UTF-8是一种能够编码所有可能字符的字符编码,它将字符存储为字节(看起来像这样)。因此,编码后的字符串可以在任何地方使用,因为几乎所有地方都支持UTF-8。当您从其他系统打开以UTF-8编码的文本文件时,您的计算机将对其进行解码,并通过它们独特的Unicode编号显示其中的字符。

当浏览器从网络接收到以UTF-8编码的字符串数据时,它将解码数据为字符串(假设浏览器使用UTF-8编码)并显示该字符串。

在Python 3中,您可以相互转换字符串和字节字符串:

>>> print('中文'.encode('utf-8'))
b'\xe4\xb8\xad\xe6\x96\x87'
>>> print(b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8'))
中文

简单来说,string 是用于在计算机上显示给人类阅读的,而 byte string 则用于存储到磁盘和数据传输。

2
“Unicode为每个字符提供了一个独特的编号”:1/ Unicode(来自Unicode Consortium)不是一种编码,而是一份字形名称列表,UTF-8或UTF-32(来自ISO)则是。在UTF中,“T”代表“转换”。2/ 您可能指的是UTF-8,但数字并不是唯一的。根据Wikipedia:“UTF-8是主流编码[...]对于前128个代码点,使用一个字节,对于其他字符,最多使用4个字节”。要为所有代码点使用唯一序列,则需要使用UTF-32,它为每个代码点分配4个字节,但此编码在实际中不常用。 - mins

4
Unicode是一种约定俗成的格式,用于字符和各种格式(例如小写/大写、换行和回车)以及其他“东西”(例如表情符号)的二进制表示,计算机能够存储Unicode表示(一系列位),无论是在内存中还是在文件中,都不会比存储ASCII表示(不同的一系列位)或任何其他表示(一系列位)差。为了进行通信,通信双方必须就使用哪种表示达成协议。因为Unicode试图表示人际和计算机间通信中使用的所有可能字符(和其他“东西”),所以对于许多字符(或“东西”)的表示,它需要更多的位数,而其他系统的表示则试图表示更有限的字符/“东西”的集合。为了“简化”,也许是为了适应历史用法,Unicode表示几乎完全转换为某些其他表示系统(例如ASCII),以便将字符存储在文件中。
Unicode并非不能用于存储文件中的字符,或通过任何通信渠道传输字符。只是它目前没有被广泛使用。
术语“字符串”并没有明确定义。“字符串”在常见用法中指一组字符/事物。在计算机中,这些字符可以以许多不同的按位表示方式之一进行存储。一个“字节串”是一组使用八位(八位被称为一个字节)表示的字符。由于现代计算机使用Unicode系统(字符由可变数量的字节表示)将字符存储在内存中,并使用字节串(由单个字节表示的字符)将字符存储到文件中,因此必须使用转换才能将内存中表示的字符移动到文件存储中。

1

字符串是一堆项目串在一起。字节字符串是一系列字节,例如 b'\xce\xb1\xce\xac' 表示 "αά"。字符字符串是一堆字符,例如 "αά"。与序列同义。

字节字符串可以直接存储到磁盘上,而字符串(字符字符串)不能直接存储在磁盘上。它们之间的映射是编码。


你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

0
简单来说,把我们的自然语言比如英语、孟加拉语、中文等想象成一种发声。但如果我们听到这些语言,我们是否都能理解呢?
普遍来说答案是否定的。所以,当我说我能够理解英语时,意味着我知道如何将这些声音编码成一些有意义的英语单词,并且我以相同的方式对这些声音进行解码以理解它们。对于任何其他语言也是如此。如果你知道这个语言,那就说明你的大脑里面有这个语言的编码-解码包,如果不知道,就没有。
数字系统也是如此。就像我们自己只能用耳朵听声音和口腔发声一样,计算机只能存储字节并读取字节。某个应用程序知道如何读取字节并解释它们(例如,要考虑多少个字节才能理解任何信息),并以相同的方式编写,以便其伙伴应用程序也能理解。但是没有理解(编码器-解码器),所有写入磁盘的数据只是一串字节。

-1
Python语言包括strbytes作为标准的“内置类型”。换句话说,它们都是类。我认为试图理解为什么Python被实现成这样并不值得。
话虽如此,strbytes非常相似。两者共享大部分相同的方法。以下方法是str类独有的:
casefold
encode
format
format_map
isdecimal
isidentifier
isnumeric
isprintable

以下方法是专属于 bytes 类的:
decode
fromhex
hex

2
是的,但这个答案相当不完整。字符串是更高级的、人类可读的构造,使用字符作为构建块,不能直接保存到磁盘。而字节是更低级别的构造,可以直接保存。字符串和字节与编码映射。如果您知道编码,可以解码一个字节串对象并将其转换为字符串对象。 - rednafi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接