<bytes> сбежал <str>Python 3 - PullRequest
       16

<bytes> сбежал <str>Python 3

2 голосов
/ 18 июня 2019

В настоящее время у меня есть Python 2.7 код, который получает <str> объекты через сокетное соединение.Во всем коде мы используем <str> объекты, сравнения и т. Д. Чтобы преобразовать в Python 3 , я обнаружил, что соединения с сокетами теперь возвращают <bytes> объекты, что требует от нас изменения всех литералов.быть похожим на b'abc' делать буквальные сравнения и т. д. Это большая работа, и хотя очевидно, почему это изменение было сделано в Python 3 Мне любопытно, есть ли более простые обходные пути.

Скажем, я получаю <bytes> b'\xf2a27' через сокетное соединение.Есть ли простой способ преобразовать эти <bytes> в <str> объект с такими же выходами в 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', который содержит символы Юникода, которых я хотел бы избежать.Хотя в этом случае len(a) = 5 и сравнения, такие как a[0] == '\xf2', работают, но я бы хотел сохранить информацию в представлении, если это возможно.

Возможно, есть более элегантное решение, которое мне не хватает?

1 Ответ

4 голосов
/ 18 июня 2019

Вы действительно должны подумать о том, что представляют собой данные, которые вы получаете, и Python 3 делает сильный шаг в этом направлении. Существует важное различие между строкой байтов, которая фактически представляет собой набор байтов, и строкой (абстрактных, Unicode) символов.

Возможно, вам придется подумать о каждой части данных в отдельности, если они могут иметь разные представления.

Давайте возьмем ваш пример b'\xf2a27', который в исходном виде, который вы получаете из сокета, представляет собой просто строку из 4 байтов: 0xf2, 0x61, 0x32, 0x37 в шестнадцатеричном или 242 , 97, 50, 55 в десятичном формате.

  1. Допустим, вы действительно хотите 4 байта из этого. Вы можете оставить его в виде байтовой строки или преобразовать ее в list или tuple байтов, если это лучше вам подходит:

    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. Если это латинский 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' - это всего лишь 4 различных способа представления одного и того же одного символа в строковом литерале (Unicode). Также 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.

    Обратите внимание на разницу в количестве байтов, выводимых в оболочку при изменении кодировки ввода / вывода по умолчанию:

    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
    

В идеале вы должны выполнить сравнение после преобразования необработанных байтов в правильный тип данных, который они представляют. Это делает ваш код более читабельным и простым в обслуживании.

Как правило, вы должны всегда преобразовывать необработанные байты в их фактический (абстрактный) тип данных, как только вы их получите. Затем сохраните его в этом абстрактном типе данных для обработки как можно дольше. Если необходимо, преобразуйте его обратно в некоторые необработанные данные на выходе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...