Python Заметки
2.31K subscribers
58 photos
2 videos
2 files
212 links
Интересные заметки и обучающие материалы по Python

Контакт: @paulwinex

⚠️ Рекламу на канале не делаю!⚠️

Хештеги для поиска:
#tricks
#libs
#pep
#basic
#regex
#qt
#django
#2to3
#source
#offtop
Download Telegram
Я очень часто работаю в REPL. Удобная штука для разработки, поисков, тестов, дебага...
Иногда случается такая ситуация, когда я делаю вызов какой-либо функции и вижу распечатку результата в консоли. И только потом понимаю что нужно было это сохранить в переменную!

>>> get_some()
<some result>

А почему бы не выполнить еще раз но уже сохранив в переменную?

>>> result = get_some()

Да, чаще всего так и делаю, но иногда это неудобно или недопустимо. Например, если результат считается долго или каждый раз он будет другой.
В этом случае выручает одна интересная особенность интерактивной консоли, это переменная "_" (нижнее подчеркивание).
Python по умолчанию сохраняет в неё результат последнего вызова если этот результат не был никуда сохранён.

>>> get_some()
<some result>
>>> print(_)
<some result>

То есть, сразу после вызова достаточно скопировать значение из этой переменной

>>> get_some()
<some result>
>>> result = _
>>> print(result)
<some result>

Это не сработает в двух случаях:

🔸 Если у вас не REPL, то есть простой запуск скрипта.

🔸 Если вы самостоятельно объявили эту переменную или сделали импорт с этим именем.

>>> _ = False
>>> get()
<some result>
>>> print(_)
False

#tricks
Как прочитать файл из ZIP архива не распаковывая этот архив?

Недавно была задача достать данные из JSON файла который лежит в ZIP архиве.

Первое, что приходит в голову – распечатать архив в TEMP и найти нужный файл. Но с Python можно сделать проще: прочитать нужный файл в архиве не извлекая всё содержимое.

Например, есть некий архив archive.zip. Где-то внутри есть файл config.json который нам надо прочитать.
Вот код который это сделает:

from zipfile import ZipFile
from pathlib import Path
import json

def get_json_from_zip(archive, file_name):
zip = ZipFile(archive)
for zipname in zip.namelist():
if Path(zipname).name == file_name:
with zip.open(zipname) as f:
return json.load(f)

config_name = 'config.json'
archive_path = 'archive.zip'
conf = get_json_from_zip(archive_path, config_name)

#tricks #libs
Как узнать кто вызвал функцию?

Порой требуется выяснить, кто именно вызвал конкретную функцию?
Конечно, можно запустить дебаг и выполнять построчно логику отслеживая откуда мы пришли в конкретную точку. Но это долго.

Чтобы просто узнать имя функции которая вызвала текущую функцию можно сделать так.

# myscript1.py
import inspect

def function1():
# распечатаем имя вызывающей функции
print('Called from:', inspect.stack()[1][3])

def function2():
function1()

function2()

Запускаем

python3 myscript1.py
Called from: function2

Но так мы увидим лишь имя предыдущей функции. А как узнать полный список вызовов?
Traceback удобен тем, что показывает цепочку вызовов, которая привела к ошибке.
Но модуль traceback также позволяет распечатать стек вызовов не выбрасывая исключение.

# myscript2.py
import traceback

def function1():
...
traceback.print_stack()
...

def function2():
function1()

function2()

Запускаем файл

python myscript2.py
File "myscript.py", line 9, in <module>
function2()
File "myscript.py", line 7, in function2
function1()
File "myscript.py", line 4, in function1
traceback.print_stack()

#tricks
Какие ассоциации у вас вызывает число 404?
Сразу вспоминается ошибка 404 Not Found (не найдено).

Именно такой номер имеет PEP 404 Python 2.8 Un-release Schedule для несуществующего релиза Python 2.8.

В нём нам сообщают что релиз 2.8 никогда не выйдет и даются советы как перейти на ветку 3.х.

#pep #2to3
Роняем Python в одну строку

Как имитировать ошибку Segmentation Fault в коде и уронить процесс интерпретатора?

🔸 Форсированно завершаем процесс через kill и exit code 11.

__import__('os').kill(__import__('os').getpid(), 11)

Но это просто быстрый выход.

🔸 Делаем что-то что вызовет ошибку со страшным сообщением что всё сломалось! Перегружаем стек вызова рекурсией, заведомо увеличив лимит до не приличия высоко.

__import__('sys').setrecursionlimit(1<<30);f=lambda f:f(f);f(f)

🔸Ломаем парсер AST

__import__('ast').literal_eval('1+1'*10**6)

Все эти вызовы приводят к такой ошибке:

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

Хм... а зачем это может быть нужно?
Может кому-то хотите устроить подлянку 👹, а может тестируете дебагер.
Таким падением можно "указать" юзеру что он зашел куда не следует чтобы больше так не делал 😨
Всё это надуманные примеры. Но тем не менее, теперь вы теперь знаете как это сделать😉.

PS. Не советую использовать этот код в рабочих проектах!

#tricks
Интересная библиотека tqdm, добавляющая прогресс бар для вашей итерации в CLI скриптах.

Что показывает прогресс бар?

- прогресс в процентах
- прогресс в количестве итераций
- потраченное и оставшееся время выполнения
- скорость выполнения в итерациях в секунду

Использовать очень просто, оберните итератор в tqdm и получите прогресс обработки!

from tqdm import tqdm
for i in tqdm(iterable_obj):
# do something

Всё сломается если внутри цикла есть какая-либо печать в stdout

#libs
Существуют фреймворки для создания десктоп приложений на базе WebGUI (HTML+CSS+JS).
Например Electron, NW.js и другие. Вся логика у них пишется на JavaScript.
Если хотите писать такие приложения с логикой на Python, то для вас есть аналог, это библиотека Eel.

🔸 Создавайте десктоп-GUI из HTML страниц со всем доступным для них функционалом.
🔸 Вызывайте Python-функции из JavaScript.
🔸 Вызывайте JavaScript функции из Python.
🔸 Есть встроенный сборщик приложения в Standalone через PyInstaller.

Eel запускает сервер и создаёт минималистичный chromium-браузер, который работает с этим сервером.

(Вы также можете открыть GUI вашего приложения и в обычном браузере, пока само приложение запущено)

Я за полчаса собрал нехитрое приложение, выполняющее Python-код введённый в HTML форме.
🌎 https://github.com/paulwinex/eel-webform-example

Для десктоп приложений идеально подойдет концепция SPA (Single Page App).
Мой элементарный пример на Vue.js
🌎 https://github.com/paulwinex/eel-vuejs-example

➡️ На почитать

#libs
Посмотрите на эту функцию

def my_func():
x = 1
y = 2
...
return x+y

Если я запущу её в таком виде, какую ошибку получу?
Похоже на пример из урока. Скорее всего будет SyntaxError из-за троеточия (...).
Но если вы выполните этот код... ОШИБКИ НЕ БУДЕТ 😳!

Почему

Всё дело в том что троеточие это одна из стандартных констант в Python, называется Ellipsis. Такая же как None, True или False.

Это не какой-то тип данных, для него нет каких-либо специальных операторов. Нигде в стандартной библиотеке он не используется.

Хотя, может в распечатке объектов с бесконечной рекурсией (но это не точно).
>>> x = []
>>> x.append(x)
>>> print(x)
[[...]]

Так где же применять эту штуку?
Прежде всего, стоит заметить что этот объект кажется достаточно неуместным для Python.
Ведь Python такой логичный и минималистичный и тут вдруг бесполезная константа которая нигде не встречается!

Но всё же давайте посмотрим как эта константа может быть использована.

🔸 Особый синтаксис срезов
В документации написано что Ellipsis используется для "extended slicing syntax".
Это значит что он используется в слайсинге, но не в стандартном. Его можно применить в расширенном слайсинге, то есть когда переопределяете метод __getitem__ в ваших классах.

>>> class C(object):
>>> def __getitem__(self, item):
>>> return item

Если вы попробуем получить нечто по индексу то в item прилетает этот индекс

>>> C()[1]
1

Но если передать в квадратные скобки несколько аргументов, то в item прилетает кортеж

>>> C()[1, 10]
(1, 10)

Вам остаётся решить как обработать такие аргументы и вернуть "срез" вашего типа.
Ну и ничто не мешает нам использовать (...) в качестве "синтаксического сахара" для реализации какой-то особой логики.

>>> C()[1, ..., 10]
(1, Ellipsis, 10)
>>> C()[3:...]
slice(3, Ellipsis, None)

Вам остаётся лишь решить как именно обработать такой запрос.
Например так:

>>> class C(object):
>>> def __getitem__(self, item):
>>> if item is Ellipsis:
>>> return "RETURN ALL"
>>> return "RETURN BY INDEX"
>>> C()[...]
RETURN ALL

Именно так сделано в numpy

🔸 Аналог для pass
Сравните две записи

def some_func():
pass

def some_func():
...

По-моему, вторая запись более ясно говорит, что здесь что-то имеется в виду и это надо дописать 😉.

Но, несмотря на то, что Ellipsis был доступен еще с древних версий 2.Х, такая запись сработает только в Python3. Для 2го троеточие доступно только в квадратных скобках.

🔸 Заглушка для значений по умолчанию

def func(x=...):
...

Альтернатива None? Совсем уж сомнительно 😖, но работает как визуальный триггер для разработчика)

Всё, больше идей пока нет)
На последок вот такая странная фиговина. Что покажет этот print() ???

#tricks
Функция round() может округлять не только дробную часть но и целую. Для этого нужно использовать отрицательное значение.

>>> round(12345.6789, 2)
12345.68
>>> round(12345.6789, -2)
12300.0

#tricks
Как создать новый тип объекта?
Очень просто! Cоздайте новый класс.

class MyType:
pass

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

Для чего?

Самое очевидное использование это Mock-объекты. Когда нам нужно подменить оригинальный объект заглушкой. Регулярно используется в авто тестах и в генераторах документации. Есть даже специальный класс для этого unittest.mock.Mock. Можем на лету создавать класс и настраивать его поведение.

>>> from unittest.mock import Mock
>>> dyn_obj = Mock()
>>> dyn_obj.return_value = 123
>>> print(dyn_obj())
123

Также можно создать инлайн тип с помощью функции type().

MyType = type("MyType", (object,),
{"func": lambda: 123,
"attr": 321})

Первый аргумент это имя нового объекта.
Второй аргумент это список родительских классов.
Третий, это неймспейс объекта, то есть его методы и атрибуты.
В неймспейсе можно указывать как просто данные так и функции.

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

# старый класс

class OldClass:
def old_method(self):
return 123

# вариант с переопределением старого класса

class OldClass(OldClass):
def new_method(self):
return self.old_method()

# тоже самое но с динамически созданным типом непосредственно в том месте где он требуется

OldClass = type('NewClass', (OldClass,), {'new_method': lambda self: self.old_method()})

>>> obj = OldClass()
>>> print (obj.new_method(), obj.old_method())
(123, 123)

Пример из практики #1
Был случай, когда я написал собственную реализацию запросов на сервер. Некий аналог requests, но очень простой, на чистых сокетах.
Это требовалось потому, что в исполняемой среде была версия Python, в которой никак не хотел работать оригинальный requests, да и слишком тяжёлый он был. При этом интерфейс ответа требовалось сохранить как в requests, но не всегда а по запросу, то есть динамически.

Пример из практики #2
Для некоторых, не совсем логичных, но всё же целей, требовалось воссоздавать типы, которые прилетели в виде JSON-данных.
Структура типа и данные инстанса были в этом JSON. Оставалось только пересобрать класс и создать инстанс. Некое подобие pickle, только немного иначе.

Но это уже достаточно сложные примеры для поста. Пожалуй, код опустим)))😊

#tricks
Типы файлов, которые может прочитать Python используя только стандартные библиотеки.

Сделал подборку модулей из стандартной библиотеки для чтения/записи различных типов файлов.
В данную подборку не вошли такие модули как pickle или zipapp, так как это форматы внутренней кухни Python.
Рассматриваем только внешние форматы.

———————————
Module: aifc
Type: aiff, aifc
Details: Audio Interchange File Format
Этот тип аудио файлов используется для записи звука в высоком качестве. Файл AIFC представляет собой сжатую версию формата AIFF.

Module: binhex
Type: hqx (BinHex4)
Details: Кодирование бинарных файлов в шестнадцатеричный текстовый формат и обратно (Binary-to-hexadecimal).

Module: bz2
Type: bzip2
Details: Архивация по алгоритму bzip2

Module: chunk
Type: aiff/aiff-c, rmff
Details: Audio Interchange File Format, Real Media File Format

Module: html
Type: html
Details: Утилиты для работы с форматом HTML

Module: json
Type: json
Details: Поддержка формата JSON.

Module: lzma
Type: xz, lzma
Details: Архивация данных с алгоритмом LZMA и поддержка старого формата XZ

Module: msilib
Type: msi
Details: Создание файлов типа Microsoft Installer, инсталляторы для Windows.

Module: sqlite3
Type: sqlite
Details: Интерфейс для работы с реляционной базой данных SQLite

Module: sunau
Type: au
Details: Интерфейс для взаимодействия с аудио файлами SunAU

Module: tarfile
Type: tar
Details: Чтение и запись файловых контейнеров в формате TAR, с поддержкой сжатия gzip, bz2 и lzma.

Module: wave
Type: wav
Details: Чтение и запись аудио файлов без сжатия формата WAV.

Module: xml
Type: xml
Details: Интерфейс для манипуляций с текстовым форматом XML.

Module: zipfile
Type: zip
Details: Чтение и запись архивов ZIP.

Module: zlib
Type: gz
Details: Чтение и запись архивов GZ с помощью библиотеки zlib

Module: csv
Type: csv
Details: Comma Separated Values, формат для записи табличных данных.

Module: configparser
Type: ini
Details: Чтение и запись формата конфигов Microsoft Windows INI file

Module: netrc
Type: netrc
Details: Парсинг файлов netrc для хранения данных аутентификации пользователей.
———————————

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

Напоминаю, что в список попали только стандартные библиотеки которые идут в поставке с Python.

#libs
Функция sub в regex может принимать функцию в качестве аргумента repl.

📄 Из документации:
If repl is a function, it is called for every non-overlapping occurrence of pattern. The function takes a single match object argument, and returns the replacement string.

То есть для каждого совпадения будет вызвана функция для вычисления замены вместо замены на одну и ту же строку для всех совпадений.
Иными словами, для замены разных совпадений на разные строки не потребуется запускать re.sub() много раз для каждой строки замены. Достаточно определить функцию, которая вернёт строку для каждого из совпадений.

Описание слишком запутанное🤔, давайте лучше рассмотрим на простом примере:

Создаем карту замены. То есть какие строки на какие требуется менять.

remap = {
'раз': '1',
'два': '2',
'три': '3',
'четыре': '4',
'пять': '5',
}

Пишем функцию поиска строки для замены. Единственным аргументом будет объект re.Match.
Используя данные этого объекта мы вычисляем замену on-the-fly!

def get_str(match: re.Match):
word = match.group(1)
return remap.get(word.lower()) or word

Пример текста.

text = '''Раз Два Три Четыре Пять
Вместе будем мы считать
Пять Четыре Три Два Раз
Мы считать научим вас
'''

Теперь запускаем re.sub и вместо строки замены (repl) подаём имя функции.

(Данный паттерн ищет отдельные слова в тексте)

>>> print(re.sub(r'(\w+)', get_str, text))
1 2 3 4 5
Вместе будем мы считать
5 4 3 2 1
Мы считать научим вас

Думаю, достаточно наглядно 🤓

#libs #regex
Startup скрипт для REPL.

Как выполнить скрипт сразу после старта интерактивной консоли Python?
Для начала понять бы зачем это может понадобиться. А причины бывают достаточно весомые

- автоматизировать одни и те же действия которые вы повторяете при старте REPL
- кастомизировать сам REPL
- объявление энвайромента или констант для ручного дебага или тестирования
итд...

В общем, как-то ускорить свою работу с REPL.

Как пример поведения, команда shell_plus в пакете django-extensions, которая перед запуском шела импортит всё самое необходимое.

Есть два способа это сделать

🔸 Флаг i
Запишите все действия в скрипт, например startup.py, и запускайте консоль такой командой:

python -i startup.py

Флаг -i означает, что после выполнения скрипта инетрпретатор не завершит процесс а перейдёт в интерактивный режим, то есть обычный RELP.

🔸 Переменная окружения PYTHONSTARTUP
Еще до старта самого Python объявляем переменную PYTHONSTARTUP, в которую следует указать путь к скрипту. Сразу после запуска REPL но до первого приглашения к вводу команды ваш скрипт исполнится.

Windows
set PYTHONSTARTUP=C:\path\to\startup.py

Linux
export PYTHONSTARTUP=/path/to/startup.py

Теперь запуск REPL будет исполнять указанный файл

Всё это относится только к интерактивной консоли REPL!

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

#tricks
Выполнение функции перед выходом.

Как вызвать функцию перед выходом из программы в Python?
Для этого нужно использовать модуль atexit. Он регистрирует функции, которые выполняются перед завершением процесса интерпретатора.

import atexit

def before_exit():
print('BEFORE EXIT')

atexit.register(before_exit)

Теперь попробуйте завершить процесс и увидите сообщение

>>> exit()
BEFORE EXIT

🔸 Для регистрации такой функции в REPL пригодится startup скрипт.
🔸 Этот функционал работает и в обычном режиме без интерактивной консоли.

#tricks #libs
Стартап-скрипт это удобное место для изменения внешнего вида REPL. Например замена символов строки приглашения или добавление автокомплитов по TAB.
Давайте заменим символы строки приглашения. Для этого нужно поменять переменные sys.ps1 и sys.ps2. Символами может быть даже эмодзи.

Напишите это в стартап-скрипте:

import sys
sys.ps1 = ' '
sys.ps2 = ' '

А как насчет динамически изменяемой строки? Это тоже можно. Создаём класс, наследованный от строки и определяем что он возвращает при печати.

import sys

class PS1(object):
def __init__(self):
self.s = True
def __str__(self):
self.s = not self.s
return '\033[97m█◣ \033[0m ' if self.s else '\033[34m█◤ \033[0m '

sys.ps1 = PS1()
sys.ps2 = '▼ '

Попробуйте поработать с этим)
___________
Еще пример python-startup скрипта с просторов гитхаба.
И пример от меня, рандомный смайл на каждой строке 😂

#tricks #libs
Чем отличаются эти термины?

Шифрование
Кодирование
Хеширование

🔸 Шифрование
Обратимое преобразование информации с целью скрыть её содержимое. Шифруются данные с помощью секретного ключа, без которого расшифровать данные в исходное состояние невозможно.

Примеры использования:
- Шифрование бинарных файлов и архивов с помощью пароля
- Шифрование текста

🔸 Кодирование
Это преобразование данных из одного вида в другой. При этом содержимое данных остаётся прежним, меняется лишь форма (способ представления данных). Всегда можно вернуть данные в прежний вид с помощью обратного кодирования (декодирование).

Примеры использования:
- Кодирование текста из ASCII в UTF-8
- Кодирование изображения из BMP в TIF
- Кодирование строки в байты

🔸 Хеширование
Необратимое преобразование данных в строку определённой длины. Содержимое хеш-строки строго зависит от исходных данных и сильно изменяется даже если в данных изменить 1 бит. Хеширование всегда выдаёт одинаковый результат при одинаковых исходных данных. "Дехешировать" строку обратно в данные невозможно.

Примеры использования:
- Хранение пароля в базе данных в виде хеш-строки не раскрывает пароль и оставляет возможность проверять правильность введённого пользователем пароля.
- Хеш-строку файла еще называют контрольной суммой. Позволяет быстро проверить одинаковы ли файлы сравнив лишь хеши этих файлов. Даже если один пиксель в картинке изменится, контрольная сумма будет другой. Очень полезно для проверки необходимости обновления файлов по сети или поиска файлов.

🐍 Модули

🔹 Для работы с криптографией в Python используются внешние модули

cryptography
PyCrypto

🔹Хеширование и другие манипуляции с паролями и токенами

crypt
hashlib
secrets
hmac

Информация здесь

🔹 Всё что связано с кодировками

codecs

#libs
⭐️ На этой неделе преодалён рубеж в 1К подписчкивов!
Спасибо вам, что читаете мои заметки 😍.
Это хорошая мотивация к развитию. Давайте вместе определим вектор этого развития. Как вы думаете, чего больше всего сейчас не хватает этому каналу?
Final Results
23%
💬 Чат для обсуждений и вопросов
20%
👍 Кнопки с реакциями под постом
55%
🎬 Видео уроки или видео курс
43%
📝 Блог с длинными статьями
12%
🎤 Стримы
Спасибо за ваши ответы. Думаю, результат опроса вполне очевиден.
Пойду обдумаю варианты реализации 😉
В Python2 была интересная возможность кодировать строку с помощью ZIP-архивации.
Ведь что такое кодирование? Просто преобразование данных из одного вида в другой. Компрессия это тоже просто другая форма данных.
Представление данных в виде ZIP просто и легко сокращает размер этих данных. Это бывает полезно или даже критично при передаче данных по медленным каналам.

# Python2
>>> my_str = 'Hello ZIP'
>>> my_zip_str = my_str.encode('zip')
>>> print my_zip_str
xЬєH═╔╔WИЄ♀ ☼╨♥️

Да, в результате это нереально прочитать, но мы же понимаем что это просто такая форма данных. Наша строка всё еще где-то там.
Пробуем преобразовать обратно.

>>> my_zip_str.decode('zip')
'Hello ZIP'

Всё на месте!
Пытливые умы заметили, что мы нифига не сэкономили 😭

>>> print len(my_str), len(my_zip_str)
9, 17

Увеличили размер почти в 2 раза!
Да, это так. Но вся сила ZIP раскрывается на больших данных

>>> my_str = 'Hello ZIP ' * 100
>>> my_zip_str = my_str.encode('zip')
>>> print len(my_str), len(my_zip_str)
1000 27

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

Да, круто, но к чемуто это я? Дело в том что в Python3 этот код не сработает.
Во-первых, кодек переименован в "zlib_codec", во-вторых подобный код вызовет ошибку и отправит нас в модуль codecs.

my_str.encode('zlib_codec')
LookupError: 'zlib_codec' is not a text encoding; use codecs.encode() to handle arbitrary codecs

Данный функционал можно повторить и в Python3 но кода получится куда больше.
И что теперь? Неужели я предлагаю заняться "некрокодингом" на Python2? Нет, не нужно тревожить пенсионера 🚑.

Всё будет ясно вследующем посте.

#libs
Вы знаете, что строки в Python можно кодировать и декодировать в разные кодировки.

>>> s = 'Привет'
>>> s.encode() # в байты
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'

>>> s.encode('ascii') # в ASCII (если получится)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)

>>> s.encode('euc_jp') # японская кодировка
b'\xa7\xb1\xa7\xe2\xa7\xda\xa7\xd3\xa7\xd6\xa7\xe4'

>>> s.encode('hz') # Simplified Chinese
b"~{'1'b'Z'S'V'd~}"

И другие...

А можно ли добавить свою кодировку в этот список?
Можно! И сейчас мы это сделаем. Давайте добавим ZIP-кодировку из Python2, о которой мы говорили в прошлом посте.

Для начала нам нужен алгоритм. Я хочу сжимать строку через ZIP и после получения байтов преобразовывать их в строку через base64 (можно и без base64). Получится такой код:

import zlib, base64

>>> orig = 'hello python'
>>> compressed = zlib.compress(orig.encode(), 5)
>>> compresed_b = base64.encodebytes(compressed)
>>> print(compresed_b)
b'eF7LSM3JyVcoqCzJyM8DAB7wBNc='

Да, строка получилась длинней чем была. Но мы помним, что всё будет иначе если исходная строка большая.

Обратный алгоритм декодирования

>>> compressed = base64.decodebytes(compresed_b)
>>> restored = zlib.decompress(compressed).decode()
>>> orig == restored
True

Отлично! 😎
Теперь, чтобы создать кодек на базе этих алгоритмов, следует воспользоваться функций codecs.register().
В неё подаётся имя функции которая должна вернуть объект codecs.CodecInfo.
Данный объект будет содержать две функции - кодирование и декодирование. Это и будет наш кодек.
Давайте назовём наш кодек просто "z". Тогда его создание будет выглядеть как-то так:

import zlib, base64, codecs

def z_encode(data):
return base64.encodebytes(zlib.compress(data.encode(), 5)), 0

def z_decode(data):
return zlib.decompress(base64.decodebytes(data)).decode(), 0

def z_search(encoding_name):
return codecs.CodecInfo(z_encode, z_decode, name='z')

codecs.register(z_search)

Тестим!

>>> s = 'Hello New codec!'
>>> s_enc = s.encode('z')
>>> print(s_enc)
b'eF7zSM3JyVfwSy1XSM5PSU1WBAAvxwV+\n'

Теперь обратно

>>> s_enc.decode('z')
'Hello New codec!'

А теперь практика!. Сжимаем JSON со списком файлов

>>> from pathlib import Path
>>> import json

>>> files = list(map(str, list(Path('~/Documents').expanduser().glob('**/*'))))
>>> print(len(files))
5390
>>> text = json.dumps(files)
>>> print(len(text))
513839
>>> enc = text.encode('z')
>>> print(len(enc))
53317

Профит почти в 10 раз!
Можно еще уменьшить размер если обойтись без BASE64. Но тогда вы получите чистые байты и как строку их передать уже не получится.

PS. А вот стандартный и самый короткой способ для ZIP-сжатия байтов в Python3

>>> import codecs
>>> codecs.encode(my_bytes, 'zip'))

(спасибо @amarovita за пример кода)
#tricks
Те, кто в Python не первый день, хорошо знают, что на число можно умножить не только число, но и другие типы. Главное, чтобы у этих типов была реализация такой операции.

# list
>>> [1] * 3
[1, 1, 1]
# tuple
>>> (2, 3) * 3
(2, 3, 2, 3, 2, 3)
# string
>>> "A" * 3
"AAA"

Так работает полиморфизм стандартных типов. Интересно здесь то, что это сработает и в том случае, когда порядок операндов обратный. То есть int умножить на [тип].

# list
>>> 3 * [1]
[1, 1, 1]
# tuple
>>> 3 * (2, 3)
(2, 3, 2, 3, 2, 3)
# string
>>> 3 * "A"
"AAA"

Если хотите реализовать такое поведение в ваших классах то следует помнить два момента:

1. Если множитель справа, то вам нужно реализовать метод __mul__, наш класс это первый операнд, то есть слева.

myType * 3

2. Если множитель слева, то вам нужно реализовать метод __rmul__, наш класс это второй операнд, справа.

3 * myType

Всё тоже самое можно делать и для других математических операторов. И если в этом примере действие и результат будут фактически одинаковыми, то бывают ситуации, когда это не так.
Например, при умножении матриц имеет значение порядок операндов.
Для других операторов, таких как деление или сдвиг, очень важно кто с какой стороны находится.

>>> 2/4, 4/2
(0.5, 2.0)

>>> 2<<3, 3<<2
(16, 12)

>>> 100%15, 15%100
(10, 15)

#tricks #basic