Python中将字符串转换为字节而不改变编码

12

我有这个问题,但我无法解决它。我有这个字符串:

data = '\xc4\xb7\x86\x17\xcd'

当我尝试对其进行编码时:

data.encode()

我得到了这个结果:

b'\xc3\x84\xc2\xb7\xc2\x86\x17\xc3\x8d'

我只想要:

b'\xc4\xb7\x86\x17\xcd'

有人知道这是什么原因,以及如何修复吗?该字符串已经存储在一个变量中,所以我无法在其前面添加文字b。


2
请注意,“不改变编码”是一个误导性的要求。当将字符串转换为字节或反之时,您必须考虑编码以便执行转换。 - poke
2个回答

19
没有考虑编码问题,无法将字符串转换为字节或将字节转换为字符串。bytes类型的整个重点在于独立于编码的字节序列,而str是一个Unicode code points序列,按设计没有唯一的字节表示形式。
因此,当你想要将一个转换成另一个时,必须明确指出要使用哪种编码来执行这个转换。转换为字节时,您必须说明如何将每个字符表示为一个字节序列;而当从字节转换时,您必须说明要使用什么方法将这些字节映射到字符。
如果不指定编码,则UTF-8是默认值,这是一个明智的默认值,因为UTF-8是普遍存在的,但它也只是许多有效编码之一。
如果您拿到原始字符串'\xc4\xb7\x86\x17\xcd',查看这些字符表示的Unicode码点。例如,\xc4LATIN CAPITAL LETTER A WITH DIAERESIS,即Ä。该字符在UTF-8中的编码为0xC3 0x84,这就解释了为什么将其编码为字节时会得到这个结果。但它在UTF-16中的编码也可以是0x00C4
关于如何正确解决这个问题以便获得所需的输出,没有明确的正确答案。Kasramvd提出的解决方案也有些不完美。如果您阅读文档中的raw_unicode_escape编解码器:

raw_unicode_escape

具有\uXXXX和其他代码点的\UXXXXXXXX的Latin-1编码。现有的反斜杠不会以任何方式被转义。它在Python pickle协议中使用。

所以这只是一个Latin-1编码,它内置了用于处理超出范围字符的回退机制。我认为这种回退机制对您的目的有些不利。对于不能表示为\xXX序列的Unicode字符,这可能会有问题:

>>> chr(256).encode('raw_unicode_escape')
b'\\u0100'

所以代码点256明确地位于Latin-1之外,这导致raw_unicode_escape编码返回字符串'\\u0100'的编码字节,将该字符转换为6个字节,与原始字符几乎没有关系(因为它是转义序列)。如果您想在此处使用Latin-1,则建议您明确使用它,而不是从raw_unicode_escape中回退到该转义序列。这将在尝试转换Latin-1区域之外的代码点时引发异常。
>>> '\xc4\xb7\x86\x17\xcd'.encode('latin1')
b'\xc4\xb7\x86\x17\xcd'
>>> chr(256).encode('latin1')
Traceback (most recent call last):
  File "<pyshell#28>", line 1, in <module>
    chr(256).encode('latin1')
UnicodeEncodeError: 'latin-1' codec can't encode character '\u0100' in position 0: ordinal not in range(256)

当然,代码点是否在Latin-1范围之外对您是否会造成问题取决于该字符串实际来自何处。但是,如果您可以保证输入仅包含有效的Latin-1字符,则很有可能您根本不需要首先使用字符串。由于您实际上正在处理某种字节,因此应查看是否可以将这些值作为字节检索。这样,您就不会引入两个编码级别,在那里您可能通过错误解释输入而破坏数据。


9
您可以使用'raw_unicode_escape'作为您的编码方式:
In [14]: bytes(data, 'raw_unicode_escape')
Out[14]: b'\xc4\xb7\x86\x17\xcd'

正如在评论中提到的那样,您也可以直接将编码传递给字符串的encode方法。

In [15]: data.encode("raw_unicode_escape")
Out[15]: b'\xc4\xb7\x86\x17\xcd'

@Jean-FrançoisFabre 在这种情况下,这甚至更好! - Mazdak
@avan989 不要“谢谢”。请接受答案。 - Jean-François Fabre
那些字符串<=>字节的转换真的很麻烦 :) 你能解释一下为什么执行默认编码会加入这些垃圾吗?(诚实地问,我不知道答案)。如果你不能,那没关系。 - Jean-François Fabre
@Jean-FrançoisFabre,您所说的“hell”和“trash”具体指什么,因为这种转换在不同情况下会有很多问题!;)) - Mazdak
@Jean-FrançoisFabre 我认为主要问题是字节是不可变的整数序列,并且在文档中声明,只允许在字节字符串中使用ASCII字符(无论所声明的源代码编码如何)。任何超过127的二进制值都必须使用适当的转义序列输入到字节字符串中。现在,在Python-3.X中,您需要在所有这些转义的字面值和所有可能的Unicode之间进行转换,还要在字符串和整数之间转换类型等等。所有这些过程将需要大量检查和时间。 - Mazdak

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