TGINSIGHT CHAT
Python Заметки
@pythonotes
EducationИнтересные заметки и обучающие материалы по Python Контакт: @paulwinex ⚠️ Рекламу на канале не делаю!⚠️ Хештеги для поиска: #tricks #libs #pep #basic #regex #qt #django #2to3 #source #offtop
Неодамнешни објави
Ознака: #libs · 124 објави
Објавено 28 јун.
В прошлом примере мы добились совпадения хеш-суммы двух архивов. Но не даёт покоя тот факт, что делается это слишком долго. Давайте сравним скорость. >>> from timeit import timeit >>> timeit("hashlib.md5( open('archive1.tar.gz', 'rb').read() ).hexdigest()", number=100, globals=globals()) # 8.6 >>> timeit("get_hash_tar1('archive1.tar.gz')", number=100, globals=globals()) # 29.8 Разница больше чем в 3 раза! Видимо потому, что кроме простого чтения байтиков мы еще применяем алгоритм разжатия данных? Кажется что для 100 итераций это время нормальное, но представьте что архив будет размером не 50 Мб а 10Гб. Время возрастёт серьезно! Попробуем сократить разрыв. Давайте считать не все данные а только хеш файлов, который посчитает сам модуль tarfile. def get_hash_tar2(path): hsum = hashlib.md5() with tarfile.open(path) as tar: for file in tar.getmembers(): hsum.update(file.chksum.to_bytes(8, byteorder='big')) return hsum.hexdigest() >>> timeit.timeit("get_hash_tar2('archive1.tar.gz')", number=100, globals=globals()) 11.5 Прирост скорости x3! Уже неплохо, почти как просчет хеша для архива без разжатия. Почему так, можно почитать в комментарии. Если коротко, мы считываем только заголовки элементов архива. Но на сколько я понял, это не отменяет чтение всего буфера из архива. А можно быстрей? Можно... #libs#tricks
Објавено 25 јун.
Для проверки целостности или идентичности файлов всегда используется проверка контрольной суммы. Это работает в большинстве случаев, но не всегда. Давайте сделаем простой тест. Создадим несколько рандомных файлов import os # create random test files files_to_archive = [] for i in range(5): name = f'example_file{i}.txt' open(name, 'wb').write(os.urandom(10**7)) files_to_archive.append(name) Я создал 5 файлов с рандомными бинарными данными. Нам сейчас неважно что там находится, главное что это некоторые файлы по 10мб. Добавим их в архив два раза import tarfile def create_tar(archive_path, files): with tarfile.open(archive_path, 'w:gz') as tar: for file in files: tar.add(file) create_tar('archive1.tar.gz', files_to_archive) create_tar('archive2.tar.gz', files_to_archive) И проверим хеш сумму >>> hashlib.md5(open("archive1.tar.gz", "rb").read()).hexdigest() 'ded8771a6ba57281f52a0e0ec38c29b8' >>> hashlib.md5(open("archive2.tar.gz", "rb").read()).hexdigest() '2a70bd3137a174393197cf67cbe91a8d' Несмотря на то, что мы сделали два одинаковых архива, внутри он не очень-то и одинаковы! Причина тут в алгоритме сжатия, который может зависеть от некоего рандома, и в записываемых мета-данных, например время создания файла архива. Даже отличие в один байт делает хеш сумму совершенно другой, несмотря на то, что файлы внутри полностью идентичны. Чтобы решить проблему следует проверять хеш сумму самих файлов внутри архива. То есть разархивировать данные без сохранения на диск и посчитать хеш для них. def get_hash_tar(path): hsum = hashlib.md5() with tarfile.open(path) as tar: for file in tar.getmembers(): hsum.update(tar.extractfile(file).read()) return hsum.hexdigest() >>> get_hash_tar('archive1.tar.gz') '0b27c443737b0a84381b827e1d9a913b' >>> get_hash_tar('archive2.tar.gz') '0b27c443737b0a84381b827e1d9a913b' Таким образом мы обошли те байты архива которые отличаются и посчитали только фактические данные файлов. #libs#tricks
Објавено 23 јун.
Недавно писал тесты для модуля, который рисует на картинках текст и разные фигуры. Обычные ошибки в коде можно поймать простым исключением. Но как убедиться что нарисовано именно то что надо? Например цвет правильный или шрифт выбран верно. Для этого нужно визуально сравнивать правильный рендер и тест. Чтобы авто тесты оставались "авто", я использовал библиотеку imgcompare С помощью неё достаточно просто сравнить два изображения и получить процентное соотношение различий между картинками. Очень удобно проверять расхождения даже в мелочах. Например если что-то пошло не так и использовался шрифт по умолчанию. К тому же мелкие различия глазами не так уж просто заметить. Видите разницу в 1 процент на картинке к посту? Нет? А она есть🐹! ➡️https://github.com/datenhahn/imgcompare #libs#tricks
Објавено 18 јун.
Модуль ensurepip, стал стандартным начиная с версии 3.4 и портирован в 2.7 Это встроенная альтернатива файлу get-pip.py. Модуль позволяет установить или обновить pip. 🔸Установка pip: python -m ensurepip 🔸Обновление до актуальной версии python -m ensurepip --upgrade 🔸Установка в директорию юзера, если вас не устраивает системный или просто нет доступа для обновления (когда не используем venv, то есть ставим глобально) python -m ensurepip --user #libs#basic
Објавено 9 јун.
Что-нибудь слышали про Лабораторию динамики флуоресценции? Если не интересуетесь конкретно этой наукой то здесь вам ловить нечего. Кроме одного момента! Сотрудник лаборатории Christoph Gohlke поддерживает неофициальную библиотеку бинарников для Python под Windows. Большая коллекция скомпиленных библиотек под разные версии Python. Именно здесь я долгое время качал старую версию PySide под Python2 и OpenImageIO, пока не потребовалось собрать её иначе. В общем, всем тем кто на Windows, советую страничку в закладки. Также будет полезно тем кто еще на Python2. Кстати, эта коллекция всё еще обновляется. #libs#2to3
Објавено 7 јун.
Библиотека pstray поможет легко создать иконку в системном трее максимально нативными средствами системы без тяжеловесного Qt и ему подобных. Здесь же есть средства создать меню, нотификации и даже radio button. #libs
Hashtags
Објавено 24 мај
Чем отличается тип bytes от bytearray? Всё просто, bytes неизменяемый тип, а bytearray изменяемый. Что это нам даёт? Как известно, строка это неизменяемый тип. Всякий раз когда вы делаете любые манипуляции со строкой вы создаёте новую строку. Если же её преобразовать в bytearray то все изменения будут происходить с оригинальным объектом без копирования. Создаём массив >>> arr = bytearray(struct.pack('=11s', b'Hello World')) bytearray(b'Hello World') Можем добавить элемент в массив >>> arr.append(0) bytearray(b'Hello World\x00') Или удалить лишний элемент по индексу >>> del arr[-1] bytearray(b'Hello World') Для добавления в строку используем extend >>> arr.extend(b'!') bytearray(b'Hello World!') С помощью pack_into() вставляем данные в имеющийся массив заменяя данные >> struct.pack_into("=6s", arr, 6, b'Python') bytearray(b'Hello Python') Достаём результат >>> struct.unpack("=12s", arr)[0] b'Hello Python' И всё это мы сделали не создавая новых объектов! Это и экономит память, и выполняется быстрей, так как мы работаем с одним и тем же объектом. #tricks#libs
Објавено 21 мај
Формат структуры поддерживает две удобные фишки ▫️ Вместо дублирования токена можно указать цифру и сразу после неё нужный токен (это вы уже знаете по прошлым постам). struct.pack('=10s', data) ▫️ Для визуального удобства токены можно разделять пробелами, но не каунтеры (цифры перед токеном) struct.pack('= 10s I I 100Q', *items) #libs#tricks
Објавено 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 файл размером 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