我认为@Anonyme2000 完整回答了这个问题,并且解决问题所需的所有细节都在那里。但是,由于这是从书本上学习的练习,其他人可能会来这里,@Anonyme2000 的答案中发生的事情的细节有点短,我会再展开一些。
字符串
Python,像许多其他语言一样有所谓的Escape Sequences,简而言之,将 \ 放在某个东西的前面意味着 - 后面的任何东西都会有特殊的含义。两个例子:
示例 1:换行符(换行符)
print("Something \nThis is a new line")
这会导致python将n解释为不是字母“n”,而是一个表示“这里应该换行”的特殊字符,这一切都感谢\n kbd> 在字母 n 的前面。 \r 也是一个“新行”,但在过去,它相当于移动 carriage printer head to the start of the line - 而不仅仅是下一行。
示例 2:字符串中的引号转义
print("I want to print this quote: \" in my string")
在这个例子中,因为我们使用引号字符 " 来开始和结束我们的字符串,所以在中间添加它会破坏字符串 (希望你明白这一点)。为了继续在文本中间添加引号,我们需要再次在引号之前添加一个转义序列字符 \,这告诉 Python 不要将引号解析为引用,但只需将其添加到字符串中。还有一种替代方法,那就是:
print('I want to print this quote: " in my string')
那是因为整个字符串是由 ' 开始和结束的,这使得 Python 能够准确地猜测(解析)实际整个字符串的开始和停止 - 这使得它 100% 确信在这种情况下引用 - 只是字符串的另一段。这些转义序列是described here,还有更多示例。
字节与字符串
为了更好地理解差异,我们将首先了解 Python 和您使用的终端如何交互。我假设您正在从cmd.exe、powershell.exe 或在 Linux 中运行您的 python 脚本,例如xterm 或其他东西。基本终端就是这样。
终端将尝试解析发送到其输出缓冲区的任何内容并将其呈现给您。您可以通过以下方式进行测试:
print('\xc3\xa5\xc3\xa4\xc3\xb6') # Most Linux systems
print('\xe5\xe4\xf6') # Most Windows systems
理论上,上面的打印之一应该让您打印一堆字节,终端知道如何呈现为 åäö。甚至您的浏览器也为您做到了这一点(有趣的旁注,这也是他们解决表情符号问题的方法,每个人都同意某些字节组合应该变成?)。我说 大多数 windows 和 Linux,因为这个结果完全取决于您在安装操作系统时选择的区域/语言。我在欧盟北部(瑞典),所以我在 Windows 中的默认 编解码器 是 ISO-8859-1,而在我的所有 Linux 机器中我都有 UTF-8。这些编解码器很重要,因为这是表示文本的人机界面。
知道了这一点,您通过 print("...") 或 sys.stdout.write("...") 发送到终端输出缓冲区的任何内容都将由终端解释并在您的语言环境中呈现。如果这不可行,就会发生错误。
这就是 Python2 和 Python3 开始成为两种不同野兽的地方。这就是你今天在这里的原因。简单来说,Python2 对字符串进行了很多自动化和魔术 "guess-work",以便您可以将字符串发送到套接字 - 并且Python 会为您处理编码。 Python2 对它们进行解析并以各种方式对其进行转换。在Python3 中,很多自动猜测工作都被删除了,因为它往往会让人们感到困惑。通过函数和套接字发送的数据本质上是薛定谔数据,有时是字符串,有时是字节。因此,现在由开发人员负责转换数据并对其进行编码。始终。
那么什么是字节 vs 字符串?
bytes 用外行的话来说,一个没有以任何方式编码的字符串,因此可以包含任何与“数据”相关的东西。它不必只是一个字符串 (a-Z, 0-9, !"#¤% 等等),它还可以包含特殊字节,例如 \x00这是一个Null byte/character。Python 永远不会尝试在 Python3 中自动解析这些数据。当这样做时:
print(b'\xe5\xe4\xf6')
与上面一样,除了您在 Python3 中将字符串定义为 bytes string 之外,Python 会改为将字节的表示而不是实际字节发送到终端缓冲区,因此,终端永远不会将它们解释为它们的实际字节。
示例 1:对数据进行编码
这将我们带到第一个示例。那么如何将包含print(b'\xe5\xe4\xf6') 的bytes 转换为终端中表示的字符,以及通过将其转换为具有特定编码的strings。在上面的例子中,\xe5\xe4\xf6 这三个字符恰好是正在制作的ISO-8859-1 编码器。我知道这一点是因为我目前在 Windows 上,如果您在终端中运行命令 chcp,您将获得您正在使用的 which code page/encoder。
为此,我可以做到:
print(b'\xe5\xe4\xf6'.decode('ISO-8859-1')
这会将bytes 对象转换为string 对象(带有编码)。
这里的问题,如果你发送这个string 到我的Linux 机器,它不知道发生了什么。因为,如果你尝试:
print(b'\x86\x84\x94'.decode('UTF-8'))
您最终会收到如下错误消息:
>>> print(b'\x86\x84\x94'.decode('UTF-8'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x86 in position 0: invalid start byte
这是因为,在UTF-8 土地上,字节\x86 不存在。所以它无法知道如何处理它。而且因为我的 Linux 机器的默认编码器是 UTF-8 - 你的 windows 数据对我的机器来说是垃圾。
这让我们...
套接字
在 Python3 和计算机的大多数物理领域中,不欢迎编码和字符串,因为它们并不是真正的东西。相反,机器在bits,简而言之,1 和 0 进行通信。其中 8 个变成了 byte,这就是 Python 的 bytes 发挥作用的地方。当从机器发送到机器(或应用程序到应用程序)时,我们必须将任何文本表示转换为bytes 序列 - 以便机器可以相互通信。无需编码,无需解析。只需 - 获取数据。
我们通过三种方式做到这一点,它们是:
print('åäö'.encode('UTF-8'))
print(bytes('åäö', 'UTF-8'))
print(b'åäö')
最后一个选项会失败 - 但我会故意这样保留它,以展示告诉 Python,“嘿,这个奇怪的东西,将它转换为字节对象”。
所有这些选项都将使用编码器*返回åäö 的bytes 表示形式*(除了最后一个,它只会使用ASCII 解析器进行编码,这充其量是有限的)。
在UTF-8 的情况下,您将返回如下内容:
b'\xc3\xa5\xc3\xa4\xc3\xb6'
这个,这是你可以在套接字上发送的东西。因为它只是一系列字节,终端、机器和应用程序不会以任何其他方式接触或处理,而不是一系列 1 和 0 *(具体来说是'11000011 10100101 11000011 10100100 11000011 10110110')
连同一些网络逻辑,这就是将在您的套接字上发送出去的内容。这就是机器的通信方式。
这是对正在发生的事情的概述。 “人”是终端,也就是您输入 åäö 的机器人机界面,终端将其编码/解析为某种编码。您的应用程序必须发挥作用才能将其转换为套接字/物理世界可以使用的东西。