Автор оригинала: Doug Hellmann.
Цель:
Кодеры и декодеры для преобразования текста между разными представления.
Модуль codecs
предоставляет потоковые и файловые интерфейсы для перекодирования данных. Чаще всего он используется для работы с текстом Unicode, но для других целей доступны и другие кодировки.
Юникод Праймер
CPython 3.x различает текст и байтовые строки. Экземпляры bytes
используют последовательность 8-битных байтовых значений. Напротив, строки str
управляются внутри как последовательность кодовых точек Юникода. Значения кодовой точки сохраняются как последовательность из 2 или 4 байтов каждая, в зависимости от параметров, заданных при компиляции Python.
Когда значения str
выводятся, они кодируются с использованием одной из нескольких стандартных схем, так что последовательность байтов может быть позже преобразована в ту же строку текста. Байты закодированного значения не обязательно совпадают со значениями кодовой точки, и кодирование определяет способ преобразования между двумя наборами значений. Для чтения данных Unicode также необходимо знать кодировку, чтобы входящие байты можно было преобразовать во внутреннее представление, используемое классом unicode
.
Наиболее распространенными кодировками для западных языков являются UTF-8
и UTF-16
, в которых для представления каждой кодовой точки используются последовательности из одного и двух байтов соответственно. Другие кодировки могут быть более эффективными для хранения языков, где большинство символов представлено кодовыми точками, которые не помещаются в два байта.
Смотрите также
Дополнительную вводную информацию о Unicode см. В списке ссылок в конце этого раздела. Особенно полезно Python Unicode HOWTO .
Кодировки
Лучший способ понять кодировку – это посмотреть на разные серии байтов, полученные при кодировании одной и той же строки разными способами. В следующих примерах эта функция используется для форматирования байтовой строки, чтобы ее было легче читать.
codecs_to_hex.py
import binascii def to_hex(t, nbytes): """Format text t as a sequence of nbyte long values separated by spaces. """ chars_per_item nbytes * 2 hex_version binascii.hexlify(t) return b' '.join( hex_version[start:start + chars_per_item] for start in range(0, len(hex_version), chars_per_item) ) if __name__ '__main__': print(to_hex(b'abcdef', 1)) print(to_hex(b'abcdef', 2))
Функция использует binascii
для получения шестнадцатеричного представления входной байтовой строки, затем вставляет пробел между каждыми байтами nbytes
перед возвратом значения.
$ python3 codecs_to_hex.py b'61 62 63 64 65 66' b'6162 6364 6566'
Первый пример кодировки начинается с печати текста 'français'
с использованием необработанного представления класса unicode
, за которым следует имя каждого символа из базы данных Unicode. Следующие две строки кодируют строку как UTF-8 и UTF-16 соответственно и показывают шестнадцатеричные значения, полученные в результате кодирования.
codecs_encodings.py
import unicodedata from codecs_to_hex import to_hex text 'français' print('Raw : {!r}'.format(text)) for c in text: print(' {!r}: {}'.format(c, unicodedata.name(c, c))) print('UTF-8 : {!r}'.format(to_hex(text.encode('utf-8'), 1))) print('UTF-16: {!r}'.format(to_hex(text.encode('utf-16'), 2)))
Результатом кодирования str
является объект bytes
.
$ python3 codecs_encodings.py Raw : 'français' 'f': LATIN SMALL LETTER F 'r': LATIN SMALL LETTER R 'a': LATIN SMALL LETTER A 'n': LATIN SMALL LETTER N 'ç': LATIN SMALL LETTER C WITH CEDILLA 'a': LATIN SMALL LETTER A 'i': LATIN SMALL LETTER I 's': LATIN SMALL LETTER S UTF-8 : b'66 72 61 6e c3 a7 61 69 73' UTF-16: b'fffe 6600 7200 6100 6e00 e700 6100 6900 7300'
Учитывая последовательность закодированных байтов в виде экземпляра bytes
, метод decode ()
преобразует их в кодовые точки и возвращает последовательность как экземпляр str
.
codecs_decode.py
from codecs_to_hex import to_hex text 'français' encoded text.encode('utf-8') decoded encoded.decode('utf-8') print('Original :', repr(text)) print('Encoded :', to_hex(encoded, 1), type(encoded)) print('Decoded :', repr(decoded), type(decoded))
Выбор используемой кодировки не меняет тип вывода.
$ python3 codecs_decode.py Original : 'français' Encoded : b'66 72 61 6e c3 a7 61 69 73'Decoded : 'français'
Примечание
Кодировка по умолчанию устанавливается в процессе запуска интерпретатора, когда сайт загружается. Обратитесь к разделу Unicode Defaults из обсуждения sys для описания настроек кодировки по умолчанию.
Работа с файлами
Кодирование и декодирование строк особенно важно при работе с операциями ввода-вывода. При записи в файл, сокет или другой поток данные должны использовать правильную кодировку. В общем, все текстовые данные должны быть декодированы из байтового представления при чтении и закодированы из внутренних значений в конкретное представление при записи. Программа может явно кодировать и декодировать данные, но в зависимости от используемой кодировки может быть нетривиальной задачей определить, было ли прочитано достаточно байтов для полного декодирования данных. codecs
предоставляет классы, которые управляют кодированием и декодированием данных, поэтому приложениям не нужно выполнять эту работу.
Самый простой интерфейс, предоставляемый codecs
, является альтернативой встроенной функции open ()
. Новая версия работает так же, как и встроенная, но добавляет два новых аргумента для указания кодировки и желаемого метода обработки ошибок.
codecs_open_write.py
from codecs_to_hex import to_hex import codecs import sys encoding sys.argv[1] filename encoding + '.txt' print('Writing to', filename) with codecs.open(filename, mode'w', encodingencoding) as f: f.write('français') # Determine the byte grouping to use for to_hex() nbytes { 'utf-8': 1, 'utf-16': 2, 'utf-32': 4, }.get(encoding, 1) # Show the raw bytes in the file print('File contents:') with open(filename, mode'rb') as f: print(to_hex(f.read(), nbytes))
Этот пример начинается со строки unicode
с «ç» и сохраняет текст в файл с использованием кодировки, указанной в командной строке.
$ python3 codecs_open_write.py utf-8 Writing to utf-8.txt File contents: b'66 72 61 6e c3 a7 61 69 73' $ python3 codecs_open_write.py utf-16 Writing to utf-16.txt File contents: b'fffe 6600 7200 6100 6e00 e700 6100 6900 7300' $ python3 codecs_open_write.py utf-32 Writing to utf-32.txt File contents: b'fffe0000 66000000 72000000 61000000 6e000000 e7000000 61000000 69000000 73000000'
Чтение данных с помощью open ()
несложно, с одной уловкой: кодировка должна быть известна заранее, чтобы правильно настроить декодер. Некоторые форматы данных, такие как XML, указывают кодировку как часть файла, но обычно это зависит от приложения. codecs
просто принимает кодировку в качестве аргумента и предполагает, что она верна.
codecs_open_read.py
import codecs import sys encoding sys.argv[1] filename encoding + '.txt' print('Reading from', filename) with codecs.open(filename, mode'r', encodingencoding) as f: print(repr(f.read()))
В этом примере считываются файлы, созданные предыдущей программой, и выводится представление полученного объекта unicode
на консоль.
$ python3 codecs_open_read.py utf-8 Reading from utf-8.txt 'français' $ python3 codecs_open_read.py utf-16 Reading from utf-16.txt 'français' $ python3 codecs_open_read.py utf-32 Reading from utf-32.txt 'français'
Порядок байтов
Многобайтовые кодировки, такие как UTF-16 и UTF-32, создают проблему при передаче данных между различными компьютерными системами либо путем прямого копирования файла, либо по сети. В разных системах используется разный порядок байтов старшего и младшего порядка. Эта характеристика данных, известная как их порядок байтов , зависит от таких факторов, как архитектура оборудования и выбор, сделанный операционной системой и разработчиком приложения. Не всегда есть способ узнать заранее, какой порядок байтов использовать для данного набора данных, поэтому многобайтовые кодировки включают маркер порядка байтов (BOM) в качестве первых нескольких байтов закодированный вывод. Например, UTF-16 определен таким образом, что 0xFFFE и 0xFEFF не являются допустимыми символами и могут использоваться для указания порядка байтов. codecs
определяет константы для маркеров порядка байтов, используемых UTF-16 и UTF-32.
codecs_bom.py
import codecs from codecs_to_hex import to_hex BOM_TYPES [ 'BOM', 'BOM_BE', 'BOM_LE', 'BOM_UTF8', 'BOM_UTF16', 'BOM_UTF16_BE', 'BOM_UTF16_LE', 'BOM_UTF32', 'BOM_UTF32_BE', 'BOM_UTF32_LE', ] for name in BOM_TYPES: print('{:12} : {}'.format( name, to_hex(getattr(codecs, name), 2)))
BOM
, BOM_UTF16
и BOM_UTF32
автоматически устанавливаются на соответствующие значения с прямым или обратным порядком байтов в зависимости от собственного порядка байтов текущей системы.
$ python3 codecs_bom.py BOM : b'fffe' BOM_BE : b'feff' BOM_LE : b'fffe' BOM_UTF8 : b'efbb bf' BOM_UTF16 : b'fffe' BOM_UTF16_BE : b'feff' BOM_UTF16_LE : b'fffe' BOM_UTF32 : b'fffe 0000' BOM_UTF32_BE : b'0000 feff' BOM_UTF32_LE : b'fffe 0000'
Порядок байтов обнаруживается и обрабатывается автоматически декодерами в кодеках
, но при кодировании можно указать явный порядок.
codecs_bom_create_file.py
import codecs from codecs_to_hex import to_hex # Pick the nonnative version of UTF-16 encoding if codecs.BOM_UTF16 codecs.BOM_UTF16_BE: bom codecs.BOM_UTF16_LE encoding 'utf_16_le' else: bom codecs.BOM_UTF16_BE encoding 'utf_16_be' print('Native order :', to_hex(codecs.BOM_UTF16, 2)) print('Selected order:', to_hex(bom, 2)) # Encode the text. encoded_text 'français'.encode(encoding) print('{:14}: {}'.format(encoding, to_hex(encoded_text, 2))) with open('nonnative-encoded.txt', mode'wb') as f: # Write the selected byte-order marker. It is not included # in the encoded text because the byte order was given # explicitly when selecting the encoding. f.write(bom) # Write the byte string for the encoded text. f.write(encoded_text)
codecs_bom_create_file.py
определяет собственный порядок байтов, а затем явно использует альтернативную форму, чтобы в следующем примере можно было продемонстрировать автоматическое определение при чтении.
$ python3 codecs_bom_create_file.py Native order : b'fffe' Selected order: b'feff' utf_16_be : b'0066 0072 0061 006e 00e7 0061 0069 0073'
codecs_bom_detection.py
не указывает порядок байтов при открытии файла, поэтому декодер использует значение спецификации в первых двух байтах файла для его определения.
codecs_bom_detection.py
import codecs from codecs_to_hex import to_hex # Look at the raw data with open('nonnative-encoded.txt', mode'rb') as f: raw_bytes f.read() print('Raw :', to_hex(raw_bytes, 2)) # Re-open the file and let codecs detect the BOM with codecs.open('nonnative-encoded.txt', mode'r', encoding'utf-16', ) as f: decoded_text f.read() print('Decoded:', repr(decoded_text))
Поскольку первые два байта файла используются для определения порядка байтов, они не включаются в данные, возвращаемые функцией read ()
.
$ python3 codecs_bom_detection.py Raw : b'feff 0066 0072 0061 006e 00e7 0061 0069 0073' Decoded: 'français'
Обработка ошибок
В предыдущих разделах указывалось на необходимость знать кодировку, используемую при чтении и записи файлов Unicode. Правильная установка кодировки важна по двум причинам. Если кодировка настроена неправильно при чтении из файла, данные будут интерпретироваться неправильно и могут быть повреждены или просто не могут быть декодированы. Не все символы Unicode могут быть представлены во всех кодировках, поэтому, если во время записи используется неправильная кодировка, будет сгенерирована ошибка и данные могут быть потеряны.
codecs
использует те же пять вариантов обработки ошибок, которые предоставляются методом encode ()
для str
и decode ()
метод bytes
, перечисленных в таблице ниже.
Режимы обработки ошибок кодека
Режим ошибки
Описание
строгий
Вызывает исключение, если данные не могут быть преобразованы.
заменять
Заменяет специальный символ-маркер для данных, которые не могут быть закодированы.
игнорировать
Пропускает данные.
xmlcharrefreplace
Символ XML (только кодировка)
обратная косая черта
escape-последовательность (только кодирование)
Ошибки кодирования
Самая распространенная ошибка – это получение UnicodeEncodeError
при записи данных Unicode в выходной поток ASCII, например в обычный файл или sys.stdout
без более надежного набора кодировок. Этот пример программы можно использовать для экспериментов с различными режимами обработки ошибок.
codecs_encode_error.py
import codecs import sys error_handling sys.argv[1] text 'français' try: # Save the data, encoded as ASCII, using the error # handling mode specified on the command line. with codecs.open('encode_error.txt', 'w', encoding'ascii', errorserror_handling) as f: f.write(text) except UnicodeEncodeError as err: print('ERROR:', err) else: # If there was no error writing to the file, # show what it contains. with open('encode_error.txt', 'rb') as f: print('File contents: {!r}'.format(f.read()))
Хотя режим strict
является наиболее безопасным для обеспечения того, чтобы приложение явно задавало правильную кодировку для всех операций ввода-вывода, он может привести к сбою программы при возникновении исключения.
$ python3 codecs_encode_error.py strict ERROR: 'ascii' codec can't encode character '\xe7' in position 4: ordinal not in range(128)
Некоторые другие режимы ошибок более гибкие. Например, replace
гарантирует, что ошибка не возникнет, за счет возможной потери данных, которые не могут быть преобразованы в запрошенную кодировку. Символ Unicode для pi по-прежнему не может быть закодирован в ASCII, но вместо того, чтобы вызывать исключение, в выходных данных символ заменяется на ?
.
$ python3 codecs_encode_error.py replace File contents: b'fran?ais'
Чтобы полностью пропустить данные о проблеме, используйте ignore
. Любые данные, которые не могут быть закодированы, отбрасываются.
$ python3 codecs_encode_error.py ignore File contents: b'franais'
Есть два варианта обработки ошибок без потерь, оба из которых заменяют символ альтернативным представлением, определенным стандартом отдельно от кодировки. xmlcharrefreplace
использует ссылку на символ XML в качестве замены (список ссылок на символы указан в документе W3C Определения объектов XML для символов ).
$ python3 codecs_encode_error.py xmlcharrefreplace File contents: b'français'
Другой схемой обработки ошибок без потерь является backslashreplace
, которая создает выходной формат, подобный значению, возвращаемому при печати repr ()
объекта unicode
. Символы Unicode заменяются на \ u
, за которым следует шестнадцатеричное значение кодовой точки.
$ python3 codecs_encode_error.py backslashreplace File contents: b'fran\\xe7ais'
Ошибки декодирования
Также возможно увидеть ошибки при декодировании данных, особенно если используется неправильная кодировка.
codecs_decode_error.py
import codecs import sys from codecs_to_hex import to_hex error_handling sys.argv[1] text 'français' print('Original :', repr(text)) # Save the data with one encoding with codecs.open('decode_error.txt', 'w', encoding'utf-16') as f: f.write(text) # Dump the bytes from the file with open('decode_error.txt', 'rb') as f: print('File contents:', to_hex(f.read(), 1)) # Try to read the data with the wrong encoding with codecs.open('decode_error.txt', 'r', encoding'utf-8', errorserror_handling) as f: try: data f.read() except UnicodeDecodeError as err: print('ERROR:', err) else: print('Read :', repr(data))
Как и в случае с кодированием, режим обработки ошибок strict
вызывает исключение, если поток байтов не может быть правильно декодирован. В этом случае UnicodeDecodeError
возникает в результате попытки преобразовать часть спецификации UTF-16 в символ с использованием декодера UTF-8.
$ python3 codecs_decode_error.py strict Original : 'français' File contents: b'ff fe 66 00 72 00 61 00 6e 00 e7 00 61 00 69 00 73 00' ERROR: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
При переключении на ignore
декодер пропускает недопустимые байты. Однако результат все еще не совсем то, что ожидалось, поскольку он включает встроенные нулевые байты.
$ python3 codecs_decode_error.py ignore Original : 'français' File contents: b'ff fe 66 00 72 00 61 00 6e 00 e7 00 61 00 69 00 73 00' Read : 'f\x00r\x00a\x00n\x00\x00a\x00i\x00s\x00'
В режиме replace
недопустимые байты заменяются \ uFFFD
, официальным символом замены Unicode, который выглядит как ромб с черным фоном и белым вопросительным знаком.
$ python3 codecs_decode_error.py replace Original : 'français' File contents: b'ff fe 66 00 72 00 61 00 6e 00 e7 00 61 00 69 00 73 00' Read : '��f\x00r\x00a\x00n\x00�\x00a\x00i\x00s\x00'
Кодирование перевода
Хотя большинство приложений будут работать с данными str
внутренне, декодируя или кодируя их как часть операции ввода-вывода, бывают случаи, когда изменение кодировки файла без сохранения этого промежуточного формата данных полезно. EncodedFile ()
берет дескриптор открытого файла, используя одну кодировку, и обертывает его классом, который переводит данные в другую кодировку по мере выполнения ввода-вывода.
codecs_encodedfile.py
from codecs_to_hex import to_hex import codecs import io # Raw version of the original data. data 'français' # Manually encode it as UTF-8. utf8 data.encode('utf-8') print('Start as UTF-8 :', to_hex(utf8, 1)) # Set up an output buffer, then wrap it as an EncodedFile. output io.BytesIO() encoded_file codecs.EncodedFile(output, data_encoding'utf-8', file_encoding'utf-16') encoded_file.write(utf8) # Fetch the buffer contents as a UTF-16 encoded byte string utf16 output.getvalue() print('Encoded to UTF-16:', to_hex(utf16, 2)) # Set up another buffer with the UTF-16 data for reading, # and wrap it with another EncodedFile. buffer io.BytesIO(utf16) encoded_file codecs.EncodedFile(buffer, data_encoding'utf-8', file_encoding'utf-16') # Read the UTF-8 encoded version of the data. recoded encoded_file.read() print('Back to UTF-8 :', to_hex(recoded, 1))
В этом примере показано чтение и запись в отдельные дескрипторы, возвращаемые функцией EncodedFile ()
. Независимо от того, используется ли дескриптор для чтения или записи, file_encoding
всегда относится к кодировке, используемой дескриптором открытого файла, переданным в качестве первого аргумента, а значение data_encoding
относится к в кодировку, используемую данными, проходящими через вызовы read ()
и write ()
.
$ python3 codecs_encodedfile.py Start as UTF-8 : b'66 72 61 6e c3 a7 61 69 73' Encoded to UTF-16: b'fffe 6600 7200 6100 6e00 e700 6100 6900 7300' Back to UTF-8 : b'66 72 61 6e c3 a7 61 69 73'
Кодировки, отличные от Unicode
Хотя в большинстве предыдущих примеров используются кодировки Unicode, кодеки
можно использовать для многих других преобразований данных. Например, Python включает кодеки для работы с base-64, bzip2, ROT-13, ZIP и другими форматами данных.
codecs_rot13.py
import codecs import io buffer io.StringIO() stream codecs.getwriter('rot_13')(buffer) text 'abcdefghijklmnopqrstuvwxyz' stream.write(text) stream.flush() print('Original:', text) print('ROT-13 :', buffer.getvalue())
Любое преобразование, которое может быть выражено как функция, принимающая один входной аргумент и возвращающая байтовую строку или строку Unicode, может быть зарегистрировано как кодек. Для кодека 'rot_13'
ввод должен быть строкой Unicode, а вывод также будет строкой Unicode.
$ python3 codecs_rot13.py Original: abcdefghijklmnopqrstuvwxyz ROT-13 : nopqrstuvwxyzabcdefghijklm
Использование кодеков
для обертывания потока данных обеспечивает более простой интерфейс, чем прямая работа с zlib.
codecs_zlib.py
import codecs import io from codecs_to_hex import to_hex buffer io.BytesIO() stream codecs.getwriter('zlib')(buffer) text b'abcdefghijklmnopqrstuvwxyz\n' * 50 stream.write(text) stream.flush() print('Original length :', len(text)) compressed_data buffer.getvalue() print('ZIP compressed :', len(compressed_data)) buffer io.BytesIO(compressed_data) stream codecs.getreader('zlib')(buffer) first_line stream.readline() print('Read first line :', repr(first_line)) uncompressed_data first_line + stream.read() print('Uncompressed :', len(uncompressed_data)) print('Same :', text uncompressed_data)
Не все системы сжатия или кодирования поддерживают чтение части данных через интерфейс потока с помощью readline ()
или read ()
, потому что им нужно найти конец сжатый сегмент, чтобы расширить его. Если программа не может удерживать в памяти весь набор несжатых данных, используйте функции инкрементного доступа библиотеки сжатия вместо кодеков
.
$ python3 codecs_zlib.py Original length : 1350 ZIP compressed : 48 Read first line : b'abcdefghijklmnopqrstuvwxyz\n' Uncompressed : 1350 Same : True
Инкрементное кодирование
Некоторые из предоставленных кодировок, особенно bz2
и zlib
, могут значительно изменить длину потока данных при работе с ним. Для больших наборов данных эти кодировки лучше работают постепенно, работая с одним небольшим фрагментом данных за раз. Для этой цели разработаны API IncrementalEncoder
и IncrementalDecoder
.
codecs_incremental_bz2.py
import codecs import sys from codecs_to_hex import to_hex text b'abcdefghijklmnopqrstuvwxyz\n' repetitions 50 print('Text length :', len(text)) print('Repetitions :', repetitions) print('Expected len:', len(text) * repetitions) # Encode the text several times to build up a # large amount of data encoder codecs.getincrementalencoder('bz2')() encoded [] print() print('Encoding:', end' ') last repetitions - 1 for i in range(repetitions): en_c encoder.encode(text, final(i last)) if en_c: print('\nEncoded : {} bytes'.format(len(en_c))) encoded.append(en_c) else: sys.stdout.write('.') all_encoded b''.join(encoded) print() print('Total encoded length:', len(all_encoded)) print() # Decode the byte string one byte at a time decoder codecs.getincrementaldecoder('bz2')() decoded [] print('Decoding:', end' ') for i, b in enumerate(all_encoded): final (i + 1) len(text) c decoder.decode(bytes([b]), final) if c: print('\nDecoded : {} characters'.format(len(c))) print('Decoding:', end' ') decoded.append(c) else: sys.stdout.write('.') print() restored b''.join(decoded) print() print('Total uncompressed length:', len(restored))
Каждый раз, когда данные передаются в кодер или декодер, его внутреннее состояние обновляется. Когда состояние согласовано (как определено кодеком), данные возвращаются, и состояние сбрасывается. До этого момента вызовы encode ()
или decode ()
не будут возвращать никаких данных. Когда передается последний бит данных, аргумент final
должен иметь значение True
, чтобы кодек знал, что нужно очистить все оставшиеся буферизованные данные.
$ python3 codecs_incremental_bz2.py Text length : 27 Repetitions : 50 Expected len: 1350 Encoding: ................................................. Encoded : 99 bytes Total encoded length: 99 Decoding: ...................................................... .................................. Decoded : 1350 characters Decoding: .......... Total uncompressed length: 1350
Данные Unicode и сетевая связь
Сетевые сокеты – это потоки байтов, и в отличие от стандартных потоков ввода и вывода они не поддерживают кодирование по умолчанию. Это означает, что программы, которые хотят отправлять или получать данные Unicode по сети, должны кодировать в байты, прежде чем они будут записаны в сокет. Этот сервер возвращает данные отправителю.
codecs_socket_fail.py
import sys import socketserver class Echo(socketserver.BaseRequestHandler): def handle(self): # Get some bytes and echo them back to the client. data self.request.recv(1024) self.request.send(data) return if __name__ '__main__': import codecs import socket import threading address ('localhost', 0) # let the kernel assign a port server socketserver.TCPServer(address, Echo) ip, port server.server_address # what port was assigned? t threading.Thread(targetserver.serve_forever) t.setDaemon(True) # don't hang on exit t.start() # Connect to the server s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((ip, port)) # Send the data # WRONG: Not encoded first! text 'français' len_sent s.send(text) # Receive a response response s.recv(len_sent) print(repr(response)) # Clean up s.close() server.socket.close()
Данные можно кодировать явно перед каждым вызовом send ()
, но отсутствие одного вызова send ()
приведет к ошибке кодирования.
$ python3 codecs_socket_fail.py Traceback (most recent call last): File "codecs_socket_fail.py", line 43, inlen_sent = s.send(text) TypeError: a bytes-like object is required, not 'str'
Использование makefile ()
для получения файлового дескриптора для сокета с последующим обертыванием его с помощью потокового считывателя или записывающего устройства означает, что строки Unicode будут кодироваться на пути в и из разъем.
codecs_socket.py
import sys import socketserver class Echo(socketserver.BaseRequestHandler): def handle(self): """Get some bytes and echo them back to the client. There is no need to decode them, since they are not used. """ data self.request.recv(1024) self.request.send(data) class PassThrough: def __init__(self, other): self.other other def write(self, data): print('Writing :', repr(data)) return self.other.write(data) def read(self, size1): print('Reading :', end' ') data self.other.read(size) print(repr(data)) return data def flush(self): return self.other.flush() def close(self): return self.other.close() if __name__ '__main__': import codecs import socket import threading address ('localhost', 0) # let the kernel assign a port server socketserver.TCPServer(address, Echo) ip, port server.server_address # what port was assigned? t threading.Thread(targetserver.serve_forever) t.setDaemon(True) # don't hang on exit t.start() # Connect to the server s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((ip, port)) # Wrap the socket with a reader and writer. read_file s.makefile('rb') incoming codecs.getreader('utf-8')(PassThrough(read_file)) write_file s.makefile('wb') outgoing codecs.getwriter('utf-8')(PassThrough(write_file)) # Send the data text 'français' print('Sending :', repr(text)) outgoing.write(text) outgoing.flush() # Receive a response response incoming.read() print('Received:', repr(response)) # Clean up s.close() server.socket.close()
В этом примере используется PassThrough
, чтобы показать, что данные кодируются перед отправкой, а ответ декодируется после того, как он получен клиентом.
$ python3 codecs_socket.py Sending : 'français' Writing : b'fran\xc3\xa7ais' Reading : b'fran\xc3\xa7ais' Reading : b'' Received: 'français'
Определение пользовательской кодировки
Поскольку Python уже поставляется с большим количеством стандартных кодеков, маловероятно, что приложению потребуется определять собственный кодировщик или декодер. Однако, когда это необходимо, в кодеках
есть несколько базовых классов, чтобы упростить процесс.
Первый шаг – понять природу преобразования, описываемого кодированием. В этих примерах будет использоваться кодировка «invertcaps», которая преобразует буквы верхнего регистра в нижний регистр, а буквы нижнего регистра – в верхний регистр. Вот простое определение функции кодирования, которая выполняет это преобразование во входной строке.
codecs_invertcaps.py
import string def invertcaps(text): """Return new string with the case of all letters switched. """ 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 ) if __name__ '__main__': print(invertcaps('ABCdef')) print(invertcaps('abcDEF'))
В этом случае кодировщик и декодер выполняют одну и ту же функцию (как и в случае с ROT-13
).
$ python3 codecs_invertcaps.py abcDEF ABCdef
Хотя это легко понять, эта реализация неэффективна, особенно для очень больших текстовых строк. К счастью, codecs
включает несколько вспомогательных функций для создания кодеков на основе карты символов , таких как invertcaps. Кодировка карты символов состоит из двух словарей. карта кодирования преобразует символьные значения из входной строки в байтовые значения на выходе, а карта декодирования работает наоборот. Сначала создайте карту декодирования, а затем используйте make_encoding_map ()
, чтобы преобразовать ее в карту кодирования. Функции C charmap_encode ()
и charmap_decode ()
используют карты для эффективного преобразования входных данных.
codecs_invertcaps_charmap.py
import codecs import string # Map every character to itself decoding_map codecs.make_identity_dict(range(256)) # Make a list of pairs of ordinal values for the lower # and uppercase letters pairs list(zip( [ord(c) for c in string.ascii_lowercase], [ord(c) for c in string.ascii_uppercase], )) # Modify the mapping to convert upper to lower and # lower to upper. decoding_map.update({ upper: lower for (lower, upper) in pairs }) decoding_map.update({ lower: upper for (lower, upper) in pairs }) # Create a separate encoding map. encoding_map codecs.make_encoding_map(decoding_map) if __name__ '__main__': print(codecs.charmap_encode('abcDEF', 'strict', encoding_map)) print(codecs.charmap_decode(b'abcDEF', 'strict', decoding_map)) print(encoding_map decoding_map)
Хотя карты кодирования и декодирования для инвертированных колпачков одинаковы, это не всегда так. make_encoding_map ()
обнаруживает ситуации, когда более одного входного символа кодируется в один и тот же выходной байт, и заменяет значение кодировки на None
, чтобы пометить кодировку как неопределенную.
$ python3 codecs_invertcaps_charmap.py (b'ABCdef', 6) ('ABCdef', 6) True
Кодировщик и декодер символьной карты поддерживают все стандартные методы обработки ошибок, описанные ранее, поэтому для соответствия этой части API не требуется никакой дополнительной работы.
codecs_invertcaps_error.py
import codecs from codecs_invertcaps_charmap import encoding_map text 'pi: \u03c0' for error in ['ignore', 'replace', 'strict']: try: encoded codecs.charmap_encode( text, error, encoding_map) except UnicodeEncodeError as err: encoded str(err) print('{:7}: {}'.format(error, encoded))
Поскольку кодовая точка Unicode для π отсутствует в карте кодирования, режим строгой обработки ошибок вызывает исключение.
$ python3 codecs_invertcaps_error.py ignore : (b'PI: ', 5) replace: (b'PI: ?', 5) strict : 'charmap' codec can't encode character '\u03c0' in position 4: character maps to
После определения карт кодирования и декодирования необходимо настроить несколько дополнительных классов и зарегистрировать кодирование. register ()
добавляет функцию поиска в реестр, чтобы, когда пользователь хочет использовать кодировку, codecs
мог ее найти. Функция поиска должна принимать единственный строковый аргумент с именем кодировки и возвращать объект CodecInfo
, если она знает кодировку, или None
, если нет.
codecs_register.py
import codecs import encodings def search1(encoding): print('search1: Searching for:', encoding) return None def search2(encoding): print('search2: Searching for:', encoding) return None codecs.register(search1) codecs.register(search2) utf8 codecs.lookup('utf-8') print('UTF-8:', utf8) try: unknown codecs.lookup('no-such-encoding') except LookupError as err: print('ERROR:', err)
Можно зарегистрировать несколько функций поиска, и каждая из них будет вызываться по очереди, пока одна из них не вернет CodecInfo
или пока список не будет исчерпан. Функция внутреннего поиска, зарегистрированная кодеками
, знает, как загружать стандартные кодеки, такие как UTF-8, из кодировок
, поэтому эти имена никогда не будут переданы пользовательским функциям поиска.
$ python3 codecs_register.py UTF-8:search1: Searching for: no-such-encoding search2: Searching for: no-such-encoding ERROR: unknown encoding: no-such-encoding
Экземпляр CodecInfo
, возвращаемый функцией поиска, сообщает кодекам
, как кодировать и декодировать с использованием всех различных поддерживаемых механизмов: без сохранения состояния, инкрементального и потокового. codecs
включает базовые классы для помощи в настройке кодировки карты символов. В этом примере все части объединяются для регистрации функции поиска, которая возвращает экземпляр CodecInfo
, настроенный для кодека invertcaps.
codecs_invertcaps_register.py
import codecs from codecs_invertcaps_charmap import encoding_map, decoding_map class InvertCapsCodec(codecs.Codec): "Stateless encoder/decoder" 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 InvertCapsIncrementalEncoder(codecs.IncrementalEncoder): def encode(self, input, finalFalse): data, nbytes codecs.charmap_encode(input, self.errors, encoding_map) return data class InvertCapsIncrementalDecoder(codecs.IncrementalDecoder): def decode(self, input, finalFalse): data, nbytes codecs.charmap_decode(input, self.errors, decoding_map) return data class InvertCapsStreamReader(InvertCapsCodec, codecs.StreamReader): pass class InvertCapsStreamWriter(InvertCapsCodec, codecs.StreamWriter): pass def find_invertcaps(encoding): """Return the codec for 'invertcaps'. """ if encoding 'invertcaps': return codecs.CodecInfo( name'invertcaps', encodeInvertCapsCodec().encode, decodeInvertCapsCodec().decode, incrementalencoderInvertCapsIncrementalEncoder, incrementaldecoderInvertCapsIncrementalDecoder, streamreaderInvertCapsStreamReader, streamwriterInvertCapsStreamWriter, ) return None codecs.register(find_invertcaps) if __name__ '__main__': # Stateless encoder/decoder encoder codecs.getencoder('invertcaps') text 'abcDEF' 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('invertcaps')(buffer) print('StreamWriter for io buffer: ') print(' writing "abcDEF"') writer.write('abcDEF') print(' buffer contents: ', buffer.getvalue()) # Incremental decoder decoder_factory codecs.getincrementaldecoder('invertcaps') decoder decoder_factory() decoded_text_parts [] for c in encoded_text: decoded_text_parts.append( decoder.decode(bytes([c]), finalFalse) ) decoded_text_parts.append(decoder.decode(b'', finalTrue)) decoded_text ''.join(decoded_text_parts) print('IncrementalDecoder converted {!r} to {!r}'.format( encoded_text, decoded_text))
Базовым классом кодировщика/декодера без сохранения состояния является Codec
. Замените encode ()
и decode ()
новой реализацией (в данном случае вызовом charmap_encode ()
и charmap_decode ()
соответственно). Каждый метод должен возвращать кортеж, содержащий преобразованные данные и количество использованных входных байтов или символов. Удобно, что charmap_encode ()
и charmap_decode ()
уже возвращают эту информацию.
IncrementalEncoder
и IncrementalDecoder
служат базовыми классами для дополнительных интерфейсов. Методы encode ()
и decode ()
инкрементных классов определены таким образом, что они возвращают только фактические преобразованные данные. Любая информация о буферизации сохраняется как внутреннее состояние. Кодировка invertcaps не требует буферизации данных (она использует сопоставление один-к-одному). Для кодировок, которые производят различный объем вывода в зависимости от обрабатываемых данных, таких как алгоритмы сжатия, BufferedIncrementalEncoder
и BufferedIncrementalDecoder
являются более подходящими базовыми классами, поскольку они управляют необработанной частью входа.
StreamReader
и StreamWriter
также нуждаются в методах encode ()
и decode ()
, и поскольку они должны возвращать то же значение, что и версия из Codec
множественного наследования, может использоваться для реализации.
$ python3 codecs_invertcaps_register.py Encoded "abcDEF" to "b'ABCdef'", consuming 6 characters StreamWriter for io buffer: writing "abcDEF" buffer contents: b'ABCdef' IncrementalDecoder converted b'ABCdef' to 'abcDEF'
Смотрите также
- стандартная библиотека документации для кодеков
- locale – Доступ к параметрам конфигурации и поведению на основе локализации и управление ими.
- io – модуль
io
включает оболочки файлов и потоков, которые также обрабатывают кодирование и декодирование. - socketserver – более подробный пример эхо-сервера см. в модуле
socketserver
. encodings
– пакет в стандартной библиотеке, содержащий реализации кодировщика/декодера, предоставляемые Python.- PEP 100 – PEP интеграции Python с Unicode.
- Unicode HOWTO – официальное руководство по использованию Unicode с Python.
- Текст против данных вместо Юникода против . 8-bit – Раздел статьи “Что нового” для Python 3.0, посвященный изменениям в обработке текста.
- Объекты Unicode Python – статья Фредрика Лунда об использовании наборов символов, отличных от ASCII, в Python 2.0.
- Как использовать UTF-8 с Python – краткое руководство Эвана Джонса по работе с Unicode, включая данные XML и Byte- Маркер заказа.
- О достоинствах Юникода – Введение в интернационализацию и Юникод, автор Тим Брей.
- О символьных строках – история обработки строк в языках программирования, пользователя Tim Bray.
- Символы против байтов – первая часть эссе Тима Брея о современной строке символов обработка для компьютерных программистов “. В этом выпуске рассматривается представление текста в памяти в форматах, отличных от байтов ASCII.
- Порядок байтов – объяснение порядка байтов в Википедии.
- Определения сущностей XML W3C для символов – спецификация XML-представлений ссылок на символы, которые не могут быть представлены в кодировке .