Получи случайную криптовалюту за регистрацию!

Python Заметки

Логотип телеграм канала @pythonotes — Python Заметки P
Логотип телеграм канала @pythonotes — Python Заметки
Адрес канала: @pythonotes
Категории: Технологии
Язык: Русский
Количество подписчиков: 2.71K
Описание канала:

Интересные заметки и обучающие материалы по Python
Контакт: @paulwinex
Хештеги для поиска:
#tricks
#libs
#pep
#basic
#regex
#qt
#django
#2to3
#source
#offtop

Рейтинги и Отзывы

3.00

3 отзыва

Оценить канал pythonotes и оставить отзыв — могут только зарегестрированные пользователи. Все отзывы проходят модерацию.

5 звезд

0

4 звезд

2

3 звезд

0

2 звезд

0

1 звезд

1


Последние сообщения 7

2021-06-07 12:01:07 Библиотека pstray поможет легко создать иконку в системном трее максимально нативными средствами системы без тяжеловесного Qt и ему подобных. Здесь же есть средства создать меню, нотификации и даже radio button.

#libs
518 views09:01
Открыть/Комментировать
2021-05-26 13:00:00 ­Уже полтора года как Python2 отправился на пенсию.
Как идёт процесс перехода на 3ю ветку?
В вебе всё более менее нормально. Django начиная с 2.0 и недавно вышедший Flask 2.0 официально больше не поддерживают Python 2.
На странице Qt for Python вторая ветка пропала из таблицы поддерживаемых версий. Теперь минимальная версия 3.8.

Но меня больше интересует готовность CG-софта. Я предполагал, что период перехода займёт от 3 до 5 лет. При том что резко, как с web, перескочить не получится и какое-то время придётся поддерживать обе ветки (как это сделали с Houdini и Maya). А ведь переделывать там ой как много.
Но, к счастью, процесс идёт достаточно бодро! Судя по этой статистике три четверти приложений уже на Ру3! Остальные догоняют.

Надеюсь план по переходу на 3й Python будет завершён к концу 2021 года.

#2to3
505 viewsedited  10:00
Открыть/Комментировать
2021-05-24 13:02:52 Чем отличается тип 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
586 views10:02
Открыть/Комментировать
2021-05-21 12:02:19 Формат структуры поддерживает две удобные фишки

Вместо дублирования токена можно указать цифру и сразу после неё нужный токен (это вы уже знаете по прошлым постам).

struct.pack('=10s', data)

Для визуального удобства токены можно разделять пробелами, но не каунтеры (цифры перед токеном)

struct.pack('= 10s I I 100Q', *items)

#libs #tricks
489 views09:02
Открыть/Комментировать
2021-05-19 12:02:26 В модуле 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
641 views09:02
Открыть/Комментировать
2021-05-17 12:03: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
651 views09:03
Открыть/Комментировать
2021-05-14 12:00:22 BMP пиксель для тестов
601 views09:00
Открыть/Комментировать
2021-05-14 12:00:00 Исследуем бинарный файл с изображением. Файл примера можно забрать сразу после этого поста.
Для простоты эксперимента, я сделал 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
637 views09:00
Открыть/Комментировать
2021-05-12 12:03:31 Давайте посмотрим что со скоростью записи в байты.
Написал тестовый скрипт который пишет 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
606 views09:03
Открыть/Комментировать
2021-05-10 12:03:08 Есть один интересный момент с запаковкой строк.

>>> 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
327 views09:03
Открыть/Комментировать