Вы знаете, что строки в 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