Модуль 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
если нет.
Экземпляр CodecInfo
, возвращаемый функцией поиска, сообщает кодекам, как кодировать и декодировать, используя все различные поддерживаемые механизмы: кодирование и декодирование без сохранения состояния, инкрементное кодирование и декодирование и потоковое кодирование и декодирование.
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'