Рубрики
Без рубрики

кодеки – Кодирование и декодирование строк

Автор оригинала: 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, in 
    len_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-представлений ссылок на символы, которые не могут быть представлены в кодировке .