<bytes> 转义 <str> Python 3

Tho*_*ind 4 python string unicode python-2.7 python-3.x

目前,我有通过套接字连接接收对象的Python 2.7代码。<str>在整个代码中,我们使用<str>对象、比较等。为了转换为Python 3,我发现套接字连接现在返回<bytes>对象,这要求我们更改所有文字,就像b'abc'进行文字比较等。这是虽然Python 3中进行此更改的原因很明显,但我很好奇是否有任何更简单的解决方法。

\n\n

假设我<bytes> b'\\xf2a27'通过套接字连接接收。有没有一种简单的方法可以将它们转换<bytes>为在Python 3.6<str>中具有相同转义的对象?我自己研究了一些解决方案但无济于事。

\n\n
a = b'\\xf2a27'.decode('utf-8', errors='backslashescape')\n
Run Code Online (Sandbox Code Playgroud)\n\n

上面的结果'\\\\xf2a27'是 withlen(a) = 7而不是原来的len(b'\\xf2a27') = 3. 索引也是错误的,这行不通,但看起来它正走在正确的道路上。

\n\n
a = b'\\xf2a27'.decode('latin1')\n
Run Code Online (Sandbox Code Playgroud)\n\n

上面的结果'\xc3\xb2a27'包含我想避免的 Unicode 字符。虽然在这种情况下len(a) = 5,比较就像a[0] == '\\xf2'工作,但如果可能的话,我想在表示中保留信息转义。

\n\n

我是否缺少更优雅的解决方案?

\n

blu*_*lub 5

你确实必须考虑你收到的数据代表什么,Python 3 在这个方向上提出了一个强点。实际表示字节集合的字节字符串和(抽象、unicode)字符字符串之间存在重要区别。

\n\n

如果每条数据可以有不同的表示形式,您可能必须单独考虑它们。

\n\n

让我们以b\'\\xf2a27\'您从套接字收到的原始形式为例,它只是一个 4 字节的字符串:十六进制的0xf2, 0x61, 0x32,或十进制的, , , 。0x37242975055

\n\n
    \n
  1. 假设您实际上想要其中的 4 个字节。您可以将其保留为字节字符串,也可以将其转换为字节listtuple字节(如果这样对您来说更好):

    \n\n
    raw_bytes = b\'\\xf2a27\'\n\nlist_of_bytes = list(raw_bytes)\n\ntuple_of_bytes = tuple(raw_bytes)\n\nif raw_bytes == b\'\\xf2a27\':\n    pass\n\nif list_of_bytes == [0xf2, 0x61, 0x32, 0x37]:\n    pass\n\nif tuple_of_bytes == (0xf2, 0x61, 0x32, 0x37):\n    pass\n
    Run Code Online (Sandbox Code Playgroud)
  2. \n
  3. 假设这实际上代表一个 32 位整数,在这种情况下您应该将其转换为 Python int。选择是以小端还是大端字节顺序编码,并确保选择正确的有符号和无符号之一。

    \n\n
    raw_bytes = b\'\\xf2a27\'\n\nsigned_little_endian, = struct.unpack(\'<i\', raw_bytes)\nsigned_little_endian = int.from_bytes(raw_bytes, byteorder=\'little\', signed=True)\n\nunsigned_little_endian, = struct.unpack(\'<I\', raw_bytes)\nunsigned_little_endian = int.from_bytes(raw_bytes, byteorder=\'little\', signed=False)\n\nsigned_big_endian, = struct.unpack(\'>i\', raw_bytes)\nsigned_big_endian = int.from_bytes(raw_bytes, byteorder=\'big\', signed=True)\n\nunsigned_big_endian, = struct.unpack(\'>I\', raw_bytes)\nunsigned_big_endian = int.from_bytes(raw_bytes, byteorder=\'big\', signed=False)\n\nif signed_litte_endian == 926048754:\n    pass\n
    Run Code Online (Sandbox Code Playgroud)
  4. \n
  5. 假设它实际上是文本。考虑一下它采用的编码。在您的情况下,它不能是 UTF-8,因为b\'\\xf2\'字节字符串无法正确解码为 UTF-8。如果它是 latin1 又名 iso8859-1 并且您确定它,那就没问题。

    \n\n
    raw_bytes = b\'\\xf2a27\'\n\ncharacter_string = raw_bytes.decode(\'iso8859-1\')\n\nif character_string == \'\\xf2a27\':\n    pass\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    如果您选择的编码是正确的,那么字符串中包含\'\\xf2\'or\'\xc3\xb2\'字符也是正确的。它仍然是单个字符。\'\xc3\xb2\'\'\\xf2\'\'\\u00f2\'\'\\U000000f2\'只是在 (unicode) 字符串文字中表示相同单个字符的 4 种不同方式。另外,len 将为 4,而不是 5。

    \n\n
    print(ord(character_string[0]))       # will be 242\nprint(hex(ord(character_string[0])))  # will be 0xf2\n\nprint(len(character_string))          # will be 4\n
    Run Code Online (Sandbox Code Playgroud)\n\n

    如果您实际观察到的长度为 5,那么您可能在错误的点上观察到了它。也许将字符串编码为 UTF-8 或通过打印到 UTF-8 终端将其隐式编码为 UTF-8 之后。

    \n\n

    请注意更改默认 I/O 编码时输出到 shell 的字节数的差异:

    \n\n
    PYTHONIOENCODING=UTF-8 python3 -c \'print(b"\\xf2a27".decode("latin1"), end="")\' | wc -c\n# will output 5\n\nPYTHONIOENCODING=latin1 python3 -c \'print(b"\\xf2a27".decode("latin1"), end="")\' | wc -c\n# will output 4\n
    Run Code Online (Sandbox Code Playgroud)
  6. \n
\n\n

理想情况下,您应该在将原始字节转换为它们表示的正确数据类型之后执行比较。这使您的代码更具可读性并且更易于维护。

\n\n

作为一般经验法则,您应该在收到原始字节后立即将其转换为其实际(抽象)数据类型。然后将其保留在抽象数据类型中以便尽可能长时间地进行处理。如有必要,将其转换回输出时的一些原始数据。

\n