TGINSIGHT CHAT
Python Заметки
@pythonotes
EducationИнтересные заметки и обучающие материалы по Python Контакт: @paulwinex ⚠️ Рекламу на канале не делаю!⚠️ Хештеги для поиска: #tricks #libs #pep #basic #regex #qt #django #2to3 #source #offtop
Неодамнешни објави
Страница 14 од 32 · 384 објави
Објавено 19 мај
В модуле struct есть класс Struct, специально для тех то любит в ООП. Возможно, кому-то будет удобней работать с классом вместо функций. Один раз указываем формат в конструкторе класса и получаем удобные свойства и методы. >>> st_head = struct.Struct('<20s') >>> st_head.format '<20s' >>> st_values = struct.Struct('=100i') >>> st_values.size 400 Для запаковки или распаковки просто передаём данные в соответствующие методы. >>> st_head.pack(b'some_name') b'some_name\x00\x00...' >>> st_values.pack(*range(100)) b'\x00\x00\x00\x00\x01\x00\x00...' #libs#tricks
Објавено 17 мај
Пора нам придумать свой бинарный формат😉 В качестве примера я запишу в файл анимационный канал из объекта Autodesk Maya. Можете открыть мой код↗️ и следить по тексту. Если у вас есть Maya, то можно даже запустить код и посмотреть на результат. 🔸Начнём запись! Сначала запишем имя канала, это будет имя атрибута, с которого пишется анимация. Сделаем предел в 64 байта. struct.pack('=64s', channel_name.encode()) Далее диапазон кадров, это два числа типа long struct.pack('=2L', start_frame, end_frame) Потом пишем анимацию в виде массива float значений. В примере запись идёт покадрово, то есть мы не загружаем весь массив ключей в память. for i in range(start_frame, end_frame + 1): val = obj.attr(channel_name).get(t=i) f.write(struct.pack('=f', val)) Всё, файл готов!😉 Итого у нас получился такой формат "=64s2L{N}f", где {N} это количество записанных значений. 🔸Теперь чтение. Считываем первые 64 байта, это имя канала. Первое с чем столкнёмся, это нулевые байты в имени канала, которые заполняют свободное пространство в выделенных 64 байтах. Просто удаляем их. struct.unpack('=64s', f.read(64))[0].rstrip(b'\x00') Читаем диапазон кадров, записанный в этот файл. frange_len = struct.calcsize('=2L') Функция struct.calcsize() возвращает размер данных в зависимости от указанного формата. Используем это чтобы прочитать нужное количество байт из файла. start_frame, end_frame = struct.unpack('=2L', f.read(frange_len)) Из диапазона рассчитаем длину анимации и забираем массив float значений. В коде есть вариант чтения по одному значению и полностью весь массив. key_count = end_frame - start_frame + 1 frmt = f'={key_count}f' keys = struct.unpack(frmt, f.read(struct.calcsize(frmt))) Всё, данные прочитаны!😎 Остальной код примера связан с манипуляцией объектами в Maya, чтобы визуально можно было увидеть, что анимация корректно восстановилась из файла. Вот таким образом мы придумали свой бинарный формат данных. Возможно, такие сложности вам покажутся излишними, но представьте когда данных действительно много, и один кадр содержит миллионы позиций 3D точек, и записать требуется 50000 кадров! Всё это в оперативку явно не поместится, придётся для каждого кадра делать отдельный файл. Если же мы можем писать данные постепенно, то это не проблема. Можно постепенно заполнять файл или писать несколько файлов паралельно. #libs#tricks
Објавено 14 мај
BMP пиксель для тестов
Објавено 14 мај
Исследуем бинарный файл с изображением. Файл примера можно забрать сразу после этого поста. Для простоты эксперимента, я сделал BMP файл размером 1х1 пиксель, сохранённый без сжатия. Наша задача — достать RGB информацию этого единственного пикселя. Файл я сделал в Photoshop и закрасил пиксель цветом [255, 128, 50]. Сохранил с глубиной цвета 24 бит (по 8 бит на канал, то есть 1 байт). Вооружившись спецификацией формата BMP мы можем рассчитать где что записано в таком файле. В начале файла записаны заголовки с различной информацией. Для её отображения можете использовать этот код. Эти данные активно используют программы-просмотрщики. Например, первые два байта это сигнатура файла (вспоминаем посты по этой тему). Полезное для нас поле - DataOffset, которое говорит где начинаются данные относительно начала файла. Offset: 54 То есть, с начала файла надо пропустить 54 байта, после чего пойдут пиксели! Так и сделаем. Открываем файл file = open('one_pixel.bmp', 'rb') Пропускаем 54 байта file.seek(54) В разделе Additional Info в спецификации написано, что порядок записи каналов такой: BGR. Поэтому забираем наши данные о каналах в таком же порядке b, g, r = struct.unpack('BBB', file.read(3)) file.close() Почему "B"? Потому что в спецификации указано, что на канал использовано по 1 байту. Проверяем print(f'R:{r} G:{g} B:{b}') R:255 G:128 B:50 Отлично, мы добыли то что нам требовалось 😊 PS: Кто в теме, может покопаться в скрипте для парсинга бинарника mb-файла (файл сцены Autodesk Maya) https://github.com/westernx/mayatools/blob/master/mayatools/binary.py #libs
Hashtags
Објавено 12 мај
Давайте посмотрим что со скоростью записи в байты. Написал тестовый скрипт который пишет 10к значений в 3к файлов с помощью JSON и через struct. Код берём здесь↗️ Вот результаты на моём железе. JSON: Array Size: 85176 File Size: 80560 Time: W:41.9381s R:24.909s BYTES: Array Size: 40033 File Size: 40000 Time: W:1.6251s R:14.5471s Через байты скорость записи х25.8 быстрей, чтение х1.7. Размер файла в 2 раза меньше. Теперь в функцию json.dump() добавим аргумент indent=4, разница станет еще больше. Запись х35, чтение х3.1, размер файла х3.2. И чем больше данных, тем больше разница. 4к файлов по 15к значений, indent=4: Запись х40.4, чтение х3.3, размер файла х3.3 Очевидно, что при записи в JSON много времени уходит на преобразование данных в строку в нужном формате. И обратная операции во время чтения. Удобство имеет свою цену) В свою очередь байты пишутся как есть без изменений и лишних знаков форматирования. Нужно лишь преобразовать каждый тип данных в массив байт. Формат находится вне файла, то есть никакой разметки, в отличие от JSON файла. Поэтому файл на много меньше по размеру. #libs
Hashtags
Објавено 10 мај
Есть один интересный момент с запаковкой строк. >>> struct.pack(f'=6s', b'python') b'python' >>> 'python'.encode() b'python' Хммм..., struct создает тип bytest но при этом для строки просит тоже bytes. На выходе получаем опять bytes без изменений. В чем логика? Ведь ничего же не поменялось! Зачем тогда нам вообще нужен struct если encode делает тоже самое? Если вам требуется записать просто одну строку какой-то рандомной длины, то паковка тут не нужна. Можете писать любые байты в файл как угодно без запаковки. А смысл паковки в том, что с помощью формата мы гарантируем правильную длину всех частей записываемых данных. Если данных слишком много, они обрезаются, если мало, то лишнее заполнится нулевым байтом. Ведь мы читаем данные, ориентируясь на заведомо установленные и предсказуемые позиции байтов в файле. >>> struct.pack(f'=3s', b'python') b'pyt' >>> struct.pack(f'=10s', b'python') b'python\x00\x00\x00\x00' Формат запаковки нужен как раз для того, чтобы фиксировать разметку файла на основе размера используемых типов данных. Вот абстрактный пример спецификации файла: ◽️128 байт : какой-то заголовок (str) ◽️8 байт : количество элементов (int) ◽️[4 байта] : массив данных (тип float до конца файла по 4 байта) Теперь мы знаем как записывать и считывать этот файл, у нас есть его спецификация. Просто берëм нужный диапазон байт и распаковываем в нужный тип данных. Если мы хоть на один байт сместимся, то данные распакуются некорректно. Когда размер данных заранее неизвестен, то, обычно, перед непосредственно данными пишут сколько они занимают места до следующего блока данных, как в моём примере. В более простых случаях это необязательно. data1 = [...] data2 = [...] struct.pack(f'=Q{len(data1)}i', len(data1), data1) struct.pack(f'=Q{len(data2)}i', len(data2), data2) Начиная с начала файлы мы точно знаем что следующие 8 байт (Q = unsigned long long) это число с количеством элементов, записанных сразу после него. И точно знаем где находится следующий блок данных. ❗️Еще раз, помимо преобразования разных типов в байты, модуль struct занимается форматированием или разметкой данных в файле. Именно это и означает что разметка находится вне файла с данными. И это критически важный момент при создании бинарных файлов! #libs
Hashtags
Објавено 7 мај
Теперь запакуем строку. В этом случае следует передавать тип данных bytes. >>> struct.pack('=s', b'a') b'a' Для записи слова следует указывать количество символов. >>> struct.pack('=5s', b'hello') b'hello' Кстати, запакованный вид соответствует исходному тексту. Всё верно, символ есть в таблице ASCII, то есть его код попадает в диапазон 0-127, он может быть записан одним байтом и имеет визуальное представление. А вот что будет если добавить символ вне ASCII >>> struct.pack(f'=s', b'ё') SyntaxError: bytes can only contain ASCII literal characters. Ошибка возникла еще на этапе создания объекта bytes, который не может содержать такой символ. Поэтому надо кодировать эти байты из строки. >>> enc = 'ёжик'.encode('utf-8') >>> struct.pack(f'={len(enc)}s', enc) b'\xd1\x91\xd0\xb6\xd0\xb8\xd0\xba' Заметьте, длина такой строки в байтах отличается от исходной длины, так как символы вне ASCII записываются двумя байтами и более. Поэтому здесь формат создаём на лету, используя получившуюся длину как каунтер токена. #libs#basic
Објавено 5 мај
Можно ли в Python создавать бинарные файлы? Конечно можно. Для этого в Python есть следующие инструменты: ▫️ тип данных bytes и bytearray ▫️ открытие файла в режиме wb (write binary) или rb (read binary) ▫️ модуль struct Про модуль struct поговорим в первую очередь. Файл в формате JSON или Yaml внутри себя содержит разметку данных. Всегда можно определить где список начался а где закончился. Где записана строка а где словарь. То есть формат записи данных содержит в себе элементы разметки данных. В binary-файле данные не имеют визуальной разметки. Это просто байты, записанные один за другим. Правила записи и чтения находятся вне файла. Модуль struct как раз и помогает с организацией данных в таком файле с помощью определения форматов записи для разных частей файла. Модуль struct преобразует Python-объекты в массив байт, готовый к записи в файл и имеющий определённый вид. Для этого всегда следует указывать формат преобразования (или, как оно здесь называется - запаковки). Формат нужен для того, чтобы выделить достаточное количество байт для записи конкретного типа объекта. В последствии с помощью того же формата будет производиться чтение. При этом следует помнить что мы говорим о типах языка С а не Python. Именно формат определяет, что записано в конкретном месте файла, число, строка или что-то еще. Вот какие токены формата у нас есть. Помимо этого, первым символом можно указать порядок байтов. На разных системах одни и те же типы данных могут записываться по-разному, поэтому желательно указать конкретный способ из доступных. Если этого не сделать, то используется символ '@', то есть нативный для текущей системы. В строке формата мы пишем в каком порядке и какие типы собираемся преобразовать в байты. Запакуем в байты простое число, токен "i". >>> import struct >>> struct.pack('=i', 10) b'\n\x00\x00\x00' Теперь несколько float, при этом нужно передавать элементы не массивом а последовательностью аргументов. >>> struct.pack('=fff', 1.0, 2.5, 4.1) b'\x00\x00\x80?\x00\x00 @33\x83@' Вместо нескольких токенов можно просто указать нужное количество элементов перед одним токеном, результат будет тот же. >>> struct.pack('=3f', 1.0, 2.5, 4.1) b'\x00\x00\x80?\x00\x00 @33\x83@' Теперь запакуем разные типы >>> data = struct.pack('=fiQ', 1.0, 4, 100500) я запаковал типы float, int и unsigned long long (очень большой int, на 8 байт) b'\x00\x00\x80?\x04\x00\x00...' Распаковка происходит аналогично, но нужно указать тот же формат, который использовался при запаковке. Результат возвращается всегда в виде кортежа. >>> struct.unpack('=fiQ', data) (1.0, 4, 100500) Как видите, ничего страшного! #lib#basic
Објавено 4 мај
⭐️ Вышел первый бета-релиз Python 3.10 Это значит что: - до стабильного релиза осталось примерно пол года - ждем информацию по ветке 3.11 #offtop
Hashtags
Објавено 3 мај
Один из самых удобных способов записать данные это использование готовых форматов, такие как JSON или YAML. Из плюсов такого подхода стоит отметить вот что: 🔸 готовый, повсеместно используемый и поддерживаемый формат 🔸 простой и понятный файл, удобочитаемый для человека 🔸 можно легко редактировать в любом текстовом редакторе без специальных программ и библиотек Но есть и минусы 🔹 затраты времени при записи файла (кодирование данных в нужный формат строки) 🔹 затраты времени при чтении файла (декодирование данных в Python объекты) 🔹 размер файла увеличивается из-за разметки данных (скобки, запятые, переносы, отступы...) 🔹 перед записью все данные должны быть помещены в память в полном объёме (не всегда) 🔹 при чтении необходимо считать весь файл в память и только потом декодировать данные Если нужно писать немного данных в несколько файлов, то затраты по времени не ощутимы. Обычно это файлы конфига или какие-либо метаданные. Это отличный вариант под такие задачи. Есть и другой поход к записи файлов - это бинарные файлы. Используется, когда данных достаточно много и никто их не собирается читать глазками😳. 🔸 очень быстрая запись 🔸 чтение значительно быстрей чем JSON, YAML итд 🔸 размер файла значительно меньше, так как нет разметки 🔸 можно записывать данные по мере поступления не загружая всё в память 🔸 можно извлечь любую часть данных независимо Из минусов 🔹 нужно определить свой формат записи данных (если не используете готовую спецификацию определённого формата) 🔹 не получится открыть файл и визуально понять что там записано, а для чтения файла потребуется знать его спецификацию. 🔹 не так-то просто создать такой файл без специальной библиотеки В таком виде удобно записывать большой массив любых однородных данных. Например, мониторинг валютной биржи или кэшированная анимация 3D геометрии. (Это не означает что нельзя записать данные разного типа, просто это будет не так удобно) Представьте себе JPG-картинку. По сути это немного мета-информации и большой массив пикселей. Тоже самое со звуком или видео файлом. Поэтому, если вы попробуете открыть картинку в текстовом редакторе вы увидите что-то вроде такого f15d cd29 a564 4578 ... 09e2 9bc4 a696 1253 ... 84e9 4de1 3b23 c24a ... 2534 5161 28e0 709d ... ... Это и есть записанные байтики. И для их чтения требуется определённый софт который знает что с ними делать. Под каждый тип файла. К чему это я? Читайте в следующем посте... #tricks#basic
Објавено 28 апр.
В PyCharm есть окно со статистикой продуктивности (Help / Productivity Guide), где отображена интересная информация об использовании IDE. В частности, можно узнать сколько нажатий на кнопки вам сэкономил автокомплит. У меня 231К за 14 месяцев ! Не плохо конечно, но клавиатура всё равно стёрлась))) 🙌⌨️😵 #offtop
Hashtags
Објавено 16 апр.
А как вам идея сделать свойство для модуля?! К сожалению, тут без внешних библиотек не обойтись. Но выглядит интересно! # my_module.py from mprop import mproperty @mproperty def x(mod): print(f"Prop from '{mod.__name__}'") return 2+2 По аналогии со свойствами класса и инстанса в функцию первым аргументом прилетает объект текущего модуля. Теперь обращаемся к функции как к объекту модуля: >>> import my_module >>> my_module.x Prop from 'my_module' 4 #tricks#libs