【问题标题】:<bytes> to escaped <str> Python 3<bytes> 转义 <str> Python 3
【发布时间】:2019-10-31 15:39:39
【问题描述】:

目前,我的 Python 2.7 代码通过套接字连接接收 &lt;str&gt; 对象。在整个代码中,我们使用&lt;str&gt; 对象、比较等。为了转换为Python 3,我发现套接字连接现在返回&lt;bytes&gt; 对象,这需要我们更改所有像 b'abc' 这样的文字来进行文字比较等。这是很多工作,虽然很明显为什么在 Python 3 中进行了这种更改,但我很好奇是否有任何更简单的解决方法.

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

a = b'\xf2a27'.decode('utf-8', errors='backslashescape')

以上产生'\\xf2a27'len(a) = 7,而不是原来的len(b'\xf2a27') = 3。索引也是错误的,这是行不通的,但它似乎正朝着正确的方向前进。

a = b'\xf2a27'.decode('latin1')

以上产生'òa27',其中包含我想避免的Unicode字符。虽然在这种情况下len(a) = 5a[0] == '\xf2' 之类的比较有效,但如果可能的话,我想保持信息在表示中转义。

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

【问题讨论】:

  • 你认为你为什么想要str
  • @StephenRauch:我不认为他们这样做 - 他们只是不想将所有字符串文字重写为字节文字。不幸的是,这是唯一明智的做法。 (搜索和替换?)
  • @Amadan,没错。这个问题本质上是苏格拉底式的。
  • @StephenRauch Amadan 所说的完全是原因,但我希望确保在乐趣开始之前不会错过更简单的解决方案。
  • Python 3 字符串 Unicode 字符串;根据定义,您无法避免 Python 3 字符串中的 Unicode 字符。如果您想避免使用非 ASCII 字符转义反斜杠转义,这是可行的;但实际上,如果你这样做,你只是在更深地挖掘漏洞百出的坑,这就是从 Python 2 更改它的原因。理智的解决方案是使用 bytes 来表示字节 - 这正是这个单独的数据类型是为了。

标签: python python-3.x string python-2.7 unicode


【解决方案1】:

您确实必须考虑您收到的数据代表什么,而 Python 3 在这个方向上具有很强的优势。实际表示字节集合的字节串和(抽象、unicode)字符串之间有一个重要区别。

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

让我们以b'\xf2a27' 为例,您从套接字接收的原始形式只是一个 4 字节的字符串:0xf20x610x320x37(十六进制)或 242 , 97, 50, 55 十进制。

  1. 假设您实际上想要 4 个字节。您可以将其保留为字节字符串,也可以将其转换为 listtuple 字节,如果这样对您有更好的帮助:

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

    raw_bytes = b'\xf2a27'
    
    signed_little_endian, = struct.unpack('<i', raw_bytes)
    signed_little_endian = int.from_bytes(raw_bytes, byteorder='little', signed=True)
    
    unsigned_little_endian, = struct.unpack('<I', raw_bytes)
    unsigned_little_endian = int.from_bytes(raw_bytes, byteorder='little', signed=False)
    
    signed_big_endian, = struct.unpack('>i', raw_bytes)
    signed_big_endian = int.from_bytes(raw_bytes, byteorder='big', signed=True)
    
    unsigned_big_endian, = struct.unpack('>I', raw_bytes)
    unsigned_big_endian = int.from_bytes(raw_bytes, byteorder='big', signed=False)
    
    if signed_litte_endian == 926048754:
        pass
    
  3. 假设它实际上是文本。考虑一下它采用什么编码。在您的情况下,它不能是 UTF-8,因为 b'\xf2' 将是一个无法正确解码为 UTF-8 的字节字符串。如果它是 latin1 a.k.a. iso8859-1 并且您确定它,那很好。

    raw_bytes = b'\xf2a27'
    
    character_string = raw_bytes.decode('iso8859-1')
    
    if character_string == '\xf2a27':
        pass
    

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

    print(ord(character_string[0]))       # will be 242
    print(hex(ord(character_string[0])))  # will be 0xf2
    
    print(len(character_string))          # will be 4
    

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

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

    PYTHONIOENCODING=UTF-8 python3 -c 'print(b"\xf2a27".decode("latin1"), end="")' | wc -c
    # will output 5
    
    PYTHONIOENCODING=latin1 python3 -c 'print(b"\xf2a27".decode("latin1"), end="")' | wc -c
    # will output 4
    

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

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

【讨论】:

    猜你喜欢
    • 2014-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-26
    相关资源
    最近更新 更多