Записать список кортежей (int, float) в поток без преобразования в строку

#python #list #io #byte

#python #Список #io #байт

Вопрос:

У меня есть список в Python, который состоит из кортежей, имеющих следующий формат: (int, float). Я хочу записать этот список в байт ввода-вывода или необработанный поток ввода-вывода без необходимости преобразовывать целые числа и / или числа с плавающей точкой в строку. Как я могу это сделать? Спасибо.

Комментарии:

1. Если вы хотите записать эти объекты в поток, вы должны преобразовать их в байты, так или иначе, т.е. сериализовать их. Если вы не хотите использовать string в качестве формата, вам нужно выбрать какой-либо другой формат. Чего вы хотите? Почему вам не нужны строки?

2. Я не хочу преобразовывать их в строки, потому что я передаю данные с сервера клиенту и хочу минимизировать размер полезной нагрузки. Мои целые числа длинные и занимают 32 байта. Мои значения с плавающей точкой составляют 24 байта. Если я преобразую их в строки, размер полезной нагрузки увеличится, потому что каждый символ в строке занимает 1 байт. Но как мне сериализовать и разсериализировать их в / из байтов? Спасибо за вашу помощь.

Ответ №1:

Существует множество форматов, которые можно использовать для сериализации объектов Python в байты. Для каждого из них есть свои плюсы и минусы.

Если данные содержат только список кортежей из целых чисел и flaots, это делает работу довольно простой.

Давайте предположим, что это данные:

 data = 100 * [(1, 1.111), (18, 1.234), (555555, 0.001), (-1, 1e70)]
  

Какой из них попадает в категорию «строк», мне не ясно. Наиболее очевидным «строковым» форматом был бы str(data) . Насколько он велик?

 >>> len(str(data))
5500
  

Это занимает 5500 байт. Вопрос требует чего-то более сжатого. Итак, мы ищем что-то намного короче 5500 байт.

JSON — очень популярный формат (он также является строкой). Насколько он велик?

 >>> len(json.dumps(data))
5500
  

Этот файл имеет тот же размер (5500 байт), но, по крайней мере, он четко определен. Может ли это быть меньше? Как насчет сжатого JSON?

 >>> len(bz2.compress(json.dumps(data).encode('utf-8')))
131
  

Это намного лучше!

Вероятно, это было очень хорошо из-за повторяющегося шаблона. Существует ли формат, который не использует архивирование? Может быть, рассолить?

 >>> len(pickle.dumps(data))
862
  

Не так хорошо, как zip (конечно!), но все равно хорошо.

Можем ли мы сделать BZipped pickle?

 >>> len(bz2.compress(pickle.dumps(data)))
155
  

Лучше, но нет причин для того, чтобы это было лучше, чем сжатый JSON.

Как насчет какого-нибудь другого формата? Вы могли бы преобразовать каждый кортеж в эквивалент этой структуры языка Си, используя модуль struct:

 struct {
    int i;
    double f;
};
  

Однако тогда вам нужно было бы знать, насколько большим может быть значение int. Python int может быть сколь угодно большим, но если вы, например, знаете, что все числа находятся в диапазоне от 0 до 255, вам нужен всего один байт. Для значения с плавающей точкой вам нужно 64 бита (т. Е. 8 байт), иначе вы потеряете точность. Таким образом, это займет около 1000 байт. Не очень хорошо.

Существуют также другие встроенные опции, описанные в документации Python по постоянству.

Вы также можете изобрести свой собственный формат.

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

Ответ №2:

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

 >>> import struct
>>> data = [(2, 1.0), (3, 2.0), (25, 55.5)]
>>> for tup in data:
    bytes_data = struct.pack("<ld", *tup)
    print(bytes_data)


b'x02x00x00x00x00x00x00x00x00x00xf0?'
b'x03x00x00x00x00x00x00x00x00x00x00@'
b'x19x00x00x00x00x00x00x00x00xc0K@'
  

Кроме того, строка, которую я использую в качестве первого аргумента pack функции, является идентификатором формата, который сообщает вам, какой тип и размер каждого числа, в данном случае l это значение с длинным знаком int, d это значение с плавающей запятой double.