Реестр кодеков и базовые классы.
Модуль codecs
определяет базовые классы для стандартных кодеков Python (кодировщик и декодировщик) и предоставляет доступ к внутреннему реестру кодеков Python, который управляет процессом поиска кодека и обработки ошибок.
Большинство стандартных кодеков - это текстовые кодировки, которые кодируют
текст в
байты, но есть также кодеки, которые кодируют текст в текст и байты в байты. В Python уже имеется большое количество стандартных кодеков и маловероятно, что приложению потребуется определять собственный кодировщик или декодировщик. Но иногда необходимо сделать свой кодек, для этих целей
модуль codecs
, предоставляет несколько базовых классов, чтобы упростить процесс.
Допустим надо сделать кодек, который будет инвертировать заглавные буквы в строчные, а строчные - в заглавные. Можно пойти простым путем и определить функцию кодирования, которая будет выполнять это преобразование для входной строки
import string
def invert(text):
"""Возвращает новую строку с преобразованными
заглавными буквами в строчные, а строчные - в заглавные
"""
return ''.join(
c.upper() if c in string.ascii_lowercase
else c.lower() if c in string.ascii_uppercase
else c
for c in text
)
>>> invert('Hello World')
'hELLO wORLD'
>>> invert('hELLO wORLD')
'Hello World'
Такая реализация неэффективна, особенно для очень больших текстовых строк. Модуль codecs
включает в себя некоторые вспомогательные функции для создания кодеков, основанных на карте символов. Карта символов состоит из двух словарей. Карта преобразует символьные значения из входной строки в байтовые значения на выходе, а карта декодирования - в обратном направлении. Сначала создадим карту декодирования, а затем с помощью функции make_encoding_map()
преобразуйте ее в карту кодирования.
# encoding_decoding_map.py
import codecs
import string
# сопоставить символы самим себе
decoding_map = codecs.make_identity_dict(range(256))
# список пар порядковых номеров соответствий
# строчной букв - заглавной
pairs = list(zip(
[ord(c) for c in string.ascii_lowercase],
[ord(c) for c in string.ascii_uppercase],
))
# теперь изменим карту 'decoding_map' так чтобы номера
# строчных букв соответствовали заглавной и наоборот
decoding_map.update({
upper: lower
for (lower, upper)
in pairs
})
decoding_map.update({
lower: upper
for (lower, upper)
in pairs
})
# Создадим отдельную карту кодирования
encoding_map = codecs.make_encoding_map(decoding_map)
>>> codecs.charmap_encode('Hello World', 'strict', encoding_map)
# (b'hELLO wORLD', 11)
>>> codecs.charmap_encode(b'hELLO wORLD', 'strict', decoding_map)
('Hello World', 11)
В данном случае карты кодирования и декодирования символов одинаковы, это может быть не всегда так. Функция make_encoding_map()
обнаруживает ситуации, когда более одного входного символа кодируется в один и тот же выходной байт и заменяет значение кодирования на None
, чтобы пометить кодировку как неопределенную.
Созданные карты символов кодирования и декодирования поддерживают все
стандартные обработчики ошибок, поэтому для соответствия этой части API не требуется никакой дополнительной работы.
Для создания полноценного кодека необходимо установить несколько дополнительных классов и зарегистрировать кодировщик и декодировщик.
Функция codecs.register()
добавляет функцию поиска в реестр кодеков, чтобы ее можно было найти. Функция поиска должна принимать один строковый аргумент с именем кодировки и возвращать
объект codecs.CodecInfo()
, если кодировка найдена или
None
если нет.
from encoding_decoding_map import encoding_map, decoding_map
import codecs
class InvertCodec(codecs.Codec):
"кодирование/декодирование без сохранения состояния"
def encode(self, input, errors='strict'):
return codecs.charmap_encode(input, errors, encoding_map)
def decode(self, input, errors='strict'):
return codecs.charmap_decode(input, errors, decoding_map)
class InvertIncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input, final=False):
data, nbytes = codecs.charmap_encode(input,
self.errors, encoding_map)
return data
class InvertIncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input, final=False):
data, nbytes = codecs.charmap_decode(input,
self.errors, decoding_map)
return data
class InvertStreamReader(InvertCodec, codecs.StreamReader):
pass
class InvertStreamWriter(InvertCodec, codecs.StreamWriter):
pass
def find_invert(encoding):
"""Return the codec for 'invert'.
"""
if encoding == 'invert':
return codecs.CodecInfo(
name='invert',
encode=InvertCodec().encode,
decode=InvertCodec().decode,
incrementalencoder=InvertIncrementalEncoder,
incrementaldecoder=InvertIncrementalDecoder,
streamreader=InvertStreamReader,
streamwriter=InvertStreamWriter,
)
return None
codecs.register(find_invert)
Для кодирования/декодирования без сохранения состояния переопределяем
Codec.encode()
и Codec.decode()
с новой реализацией, вызывая функции
charmap_encode()
и
charmap_decode()
соответственно. Каждый метод должен возвращать кортеж, содержащий преобразованные данные и количество использованных входных байтов или символов.
IncrementalEncoder
и IncrementalDecoder
служат в качестве базовых классов для дополнительных интерфейсов. Методы
encode()
и
decode()
инкрементных классов определены таким образом, что они возвращают только фактические преобразованные данные. Любая информация о буферизации сохраняется как внутреннее состояние. Созданный кодек
invert
не требует буферизации данных, т.к. использует взаимно однозначное отображение.
StreamReader
и StreamWriter
также нуждаются в методах
encode()
и
decode ()
т. к. они должны возвращать то же значение, что и версия из кодека, для реализации может использоваться множественное наследование.
# Stateless encoder/decoder
encoder = codecs.getencoder('invert')
text = 'Hello Word'
encoded_text, consumed = encoder(text)
print('Encoded "{}" to "{}", consuming {} characters'.format(
text, encoded_text, consumed))
# Stream writer
import io
buffer = io.BytesIO()
writer = codecs.getwriter('invert')(buffer)
print('StreamWriter for io buffer: ')
print(' writing "abcDEF"')
writer.write('abcDEF')
print(' buffer contents: ', buffer.getvalue())
# Incremental decoder
decoder_factory = codecs.getincrementaldecoder('invert')
decoder = decoder_factory()
decoded_text_parts = []
for c in encoded_text:
decoded_text_parts.append(
decoder.decode(bytes([c]), final=False)
)
decoded_text_parts.append(decoder.decode(b'', final=True))
decoded_text = ''.join(decoded_text_parts)
print('IncrementalDecoder converted {!r} to {!r}'.format(
encoded_text, decoded_text))
# Encoded "Hello Word" to "b'hELLO wORLD'", consuming 6 characters
# StreamWriter for io buffer:
# writing "Hello Word"
# buffer contents: b'hELLO wORLD'
# IncrementalDecoder converted b'hELLO wORLD' to 'Hello Word'