codecs — Codificación y decodificación de cadenas

Propósito:Codificadores y decodificadores para convertir texto entre diferentes representaciones.

El módulo codecs proporciona interfaces de archivos y flujos para transcodificación de datos. Se utiliza más comúnmente para trabajar con texto Unicode, pero otras codificaciones también están disponibles para otros propósitos.

Cartilla de Unicode

CPython 3.x diferencia entre las cadenas de texto y byte. Las instancias de bytes usan una secuencia de valores de bytes de 8 bits. En contraste, las cadenas de str se manejan internamente como una secuencia de puntos de código Unicode. Los valores de los puntos de código se guardan como una secuencia de 2 o 4 bytes cada uno, dependiendo de las opciones dadas cuando Python fue compilado

Cuando se emiten valores de str, se codifican utilizando uno de los varios esquemas estándar para que la secuencia de bytes pueda ser reconstruida como la misma cadena de texto después. Los bytes de los valores codificados no son necesariamente los mismos que los valores de puntos de código, y la codificación define una manera de traducir entre los dos conjuntos de valores. Leer datos Unicode también requiere conocer la codificación para que los bytes entrantes se pueden convertir a la representación interna utilizada por la clase unicode.

Las codificaciones más comunes para los idiomas occidentales son UTF-8 y UTF-16, que usan secuencias de valores de uno y dos bytes respectivamente para representar cada punto de código. Otras codificaciones pueden ser más eficiente para almacenar idiomas donde la mayoría de los caracteres son representados por puntos de código que no caben en dos bytes.

Ver también

Para obtener más información introductoria sobre Unicode, consulte la lista de referencias al final de esta sección. El Python Unicode HOWTO es especialmente útil.

Codificaciones

La mejor manera de entender las codificaciones es mirar las diferentes serie de bytes producidas por la codificación de la misma cadena en diferentes formas. Los siguientes ejemplos usan esta función para formatear la cadena de bytes para que sea más fácil de leer.

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))

La función usa binascii para obtener una representación hexadecimal de la cadena de bytes de entrada, luego inserta un espacio entre cada nbytes bytes antes de devolver el valor.

$ python3 codecs_to_hex.py

b'61 62 63 64 65 66'
b'6162 6364 6566'

El primer ejemplo de codificación comienza imprimiendo el texto 'français' usando la representación en bruto de la clase unicode, seguido por el nombre de cada carácter de la base de datos Unicode. Los dos siguientes líneas codifican la cadena como UTF-8 y UTF-16 respectivamente, y muestran los valores hexadecimales resultantes de la codificación.

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)))

El resultado de codificar un str es un objeto 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'

Dada una secuencia de bytes codificados como una instancia de bytes, el método decode() los traduce en puntos de código y devuelve la secuencia como una instancia 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))

La elección de la codificación utilizada no cambia el tipo de salida.

$ python3 codecs_decode.py

Original : 'français'
Encoded  : b'66 72 61 6e c3 a7 61 69 73' <class 'bytes'>
Decoded  : 'français' <class 'str'>

Nota

La codificación predeterminada se establece durante el proceso de inicio del intérprete, cuando se carga site. Refierete a la sección sys-unicode-defaults de la discusión de sys para una descripción de la configuración predeterminada de codificación.

Trabajar con archivos

Codificar y decodificar cadenas es especialmente importante cuando se trata con operaciones de E/S. Ya sea escribiendo en un archivo, socket u otra secuencia, los datos deben utilizar la codificación adecuada. En general, todos datos de texto deben ser decodificados a partir de su representación en bytes a medida que se leen, y codificado desde los valores internos a una representación específica a medida que se escriben. Un programa puede codificar y decodificar datos explícitamente, pero dependiendo de la codificación utilizada puede ser no trivial para determinar si se han leído suficientes bytes para decodificar completamente los datos. codecs proporciona clases que administran la codificación y decodificación de datos, por lo que las aplicaciones no tienen que hacer ese trabajo.

La interfaz más simple proporcionada por codecs es una alternativa a la función open() incorporada. La nueva versión funciona igual que la incorporada, pero agrega dos nuevos argumentos para especificar la codificación y técnica deseada de manejo de errores.

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', encoding=encoding) 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))

Este ejemplo comienza con una cadena unicode con «ç» y guarda el texto a un archivo utilizando una codificación especificada en la línea de comandos.

$ 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'

Leer los datos con open() es sencillo, con una trampa: la codificación debe ser conocida de antemano para configurar el decodificador correctamente. Algunos formatos de datos, como XML, especifican la codificación como parte del archivo, pero generalmente se depende de la aplicación para administrar. codecs simplemente toma la codificación como un argumento y la asume que es correcto.

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', encoding=encoding) as f:
    print(repr(f.read()))

Este ejemplo lee los archivos creados por el programa anterior, e imprime la representación del objeto unicode resultante a la consola.

$ 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'

Orden de bytes

Las codificaciones de múltiples bytes como UTF-16 y UTF-32 plantean un problema al transferir los datos entre diferentes sistemas informáticos, ya sea copiando el archivo directamente o con comunicación de red. Diferentes sistemas utilizan diferente orden de los bytes de orden alto y bajo. Esta característica de los datos, conocida como su endianness, depende de factores tales como la arquitectura del hardware y las elecciones hechas por el desarrollador de aplicaciones y sistemas operativos. No siempre hay un camino para saber de antemano qué orden de bytes usar para un conjunto de datos dado, por lo que las codificaciones de múltiples bytes incluyen un marcador de orden de bytes (BOM) como los primeros bytes de una salida codificada. Por ejemplo, UTF-16 se define de tal manera que 0xFFFE y 0xFEFF no son caracteres válidos, y se pueden utilizar para indicar el orden de bytes. codecs define constantes para los marcadores de orden de bytes utilizados por UTF-16 y 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 y BOM_UTF32 se configuran automáticamente en valores apropiados de big-endian o little-endian dependiendo del orden del byte nativo del sistema actual.

$ 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'

El orden de los bytes es detectado y manejado automáticamente por los decodificadores en codecs, pero se puede especificar un orden explícito para la codificación

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 determina el orden de bytes nativo, luego usa la forma alternativa explícitamente para que el siguiente ejemplo pueda demostrar auto detección al leer.

$ 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 no especifica un orden de bytes al abrir el archivo, por lo que el decodificador utiliza el valor BOM en los primeros dos bytes del archivo para determinarlo.

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))

Dado que los dos primeros bytes del archivo se utilizan para la detección del orden de bytes, no se incluyen en los datos devueltos por read().

$ python3 codecs_bom_detection.py

Raw    : b'feff 0066 0072 0061 006e 00e7 0061 0069 0073'
Decoded: 'français'

Manejo de Errores

Las secciones anteriores señalaron la necesidad de conocer la codificación que usa al leer y escribir archivos Unicode. Configurar correctamente la codificación es importante por dos razones. Si la codificación está configurada incorrectamente al leer un archivo, los datos serán interpretados mal y pueden estar dañados o simplemente no poder se decodificados. No todos los caracteres Unicode se pueden representar en todas las codificaciones, por lo que si el se utiliza la codificación errónea mientras se escribe, a continuación, se generará un error y pueden perder los datos.

codecs usa las mismas cinco opciones de manejo de errores que son proporcionadas por el método encode() de str y el método decode() de bytes, listadas en the table below.

Modos de manejo de errores de codificadores
Modo de error Descripción
strict Produce una excepción si los datos no se pueden convertir.
replace Sustituye un carácter marcador especial por datos que no pueden codificarse.
ignore Ignora los datos.
xmlcharrefreplace Carácter XML (solo codificación).
backslashreplace Secuencia de escape (solo codificación).

Errores de codificación

La condición de error más común es recibir un UnicodeEncodeError al escribir datos Unicode en un flujo de salida ASCII, como un archivo regular o sys.stdout sin un conjunto de codificación más robusto. Este programa de ejemplo puede ser utilizado para experimentar con los diferentes modos de manejo de errores.

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',
                     errors=error_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()))

Mientras que el modo strict es el más seguro para garantizar que una aplicación establece explícitamente la codificación correcta para todas las operaciones de E/S, puede llevar a la caída del programa cuando se produce una excepción.

$ python3 codecs_encode_error.py strict

ERROR: 'ascii' codec can't encode character '\xe7' in position
4: ordinal not in range(128)

Algunos de los otros modos de error son más flexibles. Por ejemplo, replace garantiza que no se genere ningún error, a expensas de posible pérdida de datos que no puedan ser convertidos a la codificación solicitada. El carácter Unicode para pi no se puede codificar en ASCII, pero en lugar de provocar una excepción, el carácter es reemplazado con ? en la salida.

$ python3 codecs_encode_error.py replace

File contents: b'fran?ais'

Para omitir completamente los datos problemáticos, usa ignore. Cualquier dato que no se puede codificar se descarta.

$ python3 codecs_encode_error.py ignore

File contents: b'franais'

Hay dos opciones de manejo de errores sin pérdida, las cuales reemplazan el carácter con una representación alternativa definida por un estándar separado de la codificación. xmlcharrefreplace utiliza una referencia de caracteres XML como sustituto (la lista de referencias de caracteres se especifica en el documento del W3C XML Entity Definitions for Characters).

$ python3 codecs_encode_error.py xmlcharrefreplace

File contents: b'fran&#231;ais'

El otro esquema de manejo de errores sin pérdida es backslashreplace, que produce un formato de salida como el valor devuelto cuando repr() de un objeto unicode se imprime. Los caracteres Unicode son reemplazados con \u seguido del valor hexadecimal del punto de código.

$ python3 codecs_encode_error.py backslashreplace

File contents: b'fran\\xe7ais'

Errores de decodificación

También es posible ver errores al decodificar datos, especialmente si se utiliza la codificación incorrecta.

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',
                 errors=error_handling) as f:
    try:
        data = f.read()
    except UnicodeDecodeError as err:
        print('ERROR:', err)
    else:
        print('Read         :', repr(data))

Al igual que con la codificación, el modo de manejo de errores strict genera una excepción si el flujo de bytes no se puede decodificar correctamente. En este caso, un UnicodeDecodeError resulta de intentar convertir parte del BOM UTF-16 a un carácter utilizando el decodificador 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

Cambiar a ignore hace que el decodificador omita los bytes inválidos. Sin embargo, el resultado todavía no es exactamente lo que se espera, ya que incluye bytes nulos.

$ 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'

En el modo replace los bytes inválidos se reemplazan con \uFFFD, el carácter oficial de reemplazo de Unicode, que parece un diamante con un fondo negro que contiene un signo de interrogación blanco.

$ 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'

Traducción de la codificación

Aunque la mayoría de las aplicaciones funcionarán con datos str internamente, decodificándolos o codificándolos como parte de una operación de E/S, hay momentos en los que cambiar la codificación de un archivo sin mantener el formato de datos intermedios es útil. EncodedFile() toma un identificador de un archivo abierto usando una codificación y lo envuelve con una clase que traduce los datos a otra codificación a medida que se produce la E/S.

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))

Este ejemplo muestra cómo leer y escribir en identificadores separados devueltos por EncodedFile(). No importa si se utiliza el identificador para leer o escribir, el file_encoding siempre se refiere a la codificación en uso por el identificador de archivo abierto pasado como primer argumento, y el valor data_encoding se refiere a la codificación en uso por los datos pasados por las llamadas read() y 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'

Codificaciones No Unicode

Aunque la mayoría de los ejemplos anteriores utilizan codificaciones Unicode, codecs se puede usar para muchas otras traducciones de datos. Por ejemplo, Python incluye codecs para trabajar con base-64, bzip2, ROT-13, ZIP, y otros formatos de datos.

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())

Cualquier transformación que se pueda expresar como una función que toma un solo argumento de entrada y devuelve una cadena de bytes o Unicode puede ser registrado como un codec. Para el codec 'rot_13', la entrada debe ser una cadena Unicode y la salida también será una cadena Unicode.

$ python3 codecs_rot13.py

Original: abcdefghijklmnopqrstuvwxyz
ROT-13  : nopqrstuvwxyzabcdefghijklm

Usar codecs para envolver un flujo de datos proporciona una interfaz más simple que trabajar directamente con 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)

No todos los sistemas de compresión o codificación admiten la lectura de parte de los datos a través de la interfaz de flujo usando readline() o read() porque necesitan encontrar el final de un segmento comprimido para expandirlo. Si un programa no puede mantener el conjunto completo de datos sin comprimirlo en la memoria, usa las características de acceso incremental de la biblioteca de compresión, en lugar de codecs.

$ python3 codecs_zlib.py

Original length : 1350
ZIP compressed  : 48
Read first line : b'abcdefghijklmnopqrstuvwxyz\n'
Uncompressed    : 1350
Same            : True

Codificación incremental

Algunas de las codificaciones proporcionadas, especialmente bz2 y zlib, pueden cambiar dramáticamente la longitud del flujo de datos a medida que trabajan en ellos. Para grandes conjuntos de datos, estas codificaciones funcionan mejor incrementalmente, trabajando en una pequeña porción de datos a la vez. Las interfaces de IncrementalEncoder y IncrementalDecoder están diseñadas para este fin.

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))

Cada vez que se pasan datos al codificador o al decodificador, su estado interno se actualiza. Cuando el estado es consistente (como lo define el codec), se devuelven los datos y se restablece el estado. Hasta ese punto, llamas a encode() o decode() no devolverá ningún dato. Cuando el último bit de datos se pasa, el argumento final se debe establecer en True para que el codec sepa que tiene que vaciar cualquier dato restante almacenado .

$ 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

Comunicación de datos y redes Unicode

Los sockets de red son flujos de bytes, y a diferencia de la entrada estándar y las flujos de salida no admiten la codificación de forma predeterminada. Eso significa que programas que desean enviar o recibir datos Unicode a través de la red debe codificar en bytes antes de que escribir en un socket. Este servidor repite los datos que recibe de vuelta al remitente.

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(target=server.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()

Los datos podrían codificarse explícitamente antes de cada llamada a send(), pero perder una llamada a send() resultaría en un error de codificación.

$ python3 codecs_socket_fail.py

Traceback (most recent call last):
  File "codecs_socket_fail.py", line 43, in <module>
    len_sent = s.send(text)
TypeError: a bytes-like object is required, not 'str'

Usando makefile() para obtener un identificador de archivo para el socket, y luego, envolviéndolo con un lector o escritor basado en flujo, significa que las cadenas Unicode se codificarán en la entrada y salida del socket.

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, size=-1):
        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(target=server.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()

Este ejemplo usa PassThrough para mostrar que los datos son codificados antes de ser enviados, y la respuesta se decodifica después de ser recibida en el cliente.

$ python3 codecs_socket.py

Sending : 'français'
Writing : b'fran\xc3\xa7ais'
Reading : b'fran\xc3\xa7ais'
Reading : b''
Received: 'français'

Definir de una codificación personalizada

Ya que Python viene con una gran cantidad de codecs estándar ya, es poco probable que una aplicación necesite definir un codificador o decodificador personalizado. Cuando es necesario, sin embargo, hay varias clases base en codecs para facilitar el proceso.

El primer paso es entender la naturaleza de la transformación descrita por la codificación. Estos ejemplos utilizarán un codificación «invertcaps» que convierte letras mayúsculas a minúsculas y letras minúsculas a mayúsculas. Aquí hay una definición simple de una función de codificación que realiza esta transformación en una cadena de entrada.

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'))

En este caso, el codificador y el decodificador tienen la misma función (como es también el caso con ROT-13).

$ python3 codecs_invertcaps.py

abcDEF
ABCdef

Aunque es fácil de entender, esta implementación no es eficiente, especialmente para cadenas de texto muy grandes. Por suerte, codecs incluye algunas funciones de ayuda para crear codecs basados en mapas de carácteres como invertcaps. Una codificación de mapa de caracteres está compuesta por dos diccionarios. El mapa de codificación convierte los caracteres de la cadena de entrada a los valores de byte en la salida y el mapa de decodificación va de regreso. Crea el mapa de decodificación primero, y luego use make_encoding_map() para convertirlo en un mapa de codificación. Las funciones de C charmap_encode() y charmap_decode() usan los mapas para convertir sus datos de entrada eficientemente.

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)

Aunque los mapas de codificación y decodificación de invertcaps son los mismos, puede que no siempre sea el caso. make_encoding_map() detecta situaciones en las que más de un carácter de entrada se codifica en el mismo byte de salida y reemplaza el valor de codificación con None para marcar la codificación como indefinida.

$ python3 codecs_invertcaps_charmap.py

(b'ABCdef', 6)
('ABCdef', 6)
True

El codificador y decodificador de mapa de caracteres son compatibles con todos los métodos estándares de manejo de errores descritos anteriormente, por lo que no se necesita trabajo adicional para cumplir con esa parte de la interfaz de programación.

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))

Debido a que el punto de código Unicode para π no está en el mapa de codificación, el modo de manejo de error estricto produce una excepción.

$ 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 <undefined>

Después de definir los mapas de codificación y decodificación, es necesario configurar algunas clases adicionales, y la codificación debe ser registrada. register() agrega una función de búsqueda al registro para que cuando un usuario quiera usar la codificación codecs pueda localizarla. La función de búsqueda debe tomar un solo argumento de cadena con el nombre de la codificación, y devolver un objeto CodecInfo si conoce la codificación, o None si no lo hace.

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)

Se pueden registrar múltiples funciones de búsqueda, y cada una será llamada alternadamente hasta que una devuelva un CodecInfo o la lista sea agotada. La función de búsqueda interna registrada por codecs sabe cómo cargar los codecs estándar como UTF-8 desde encodings, por lo que esos nombres nunca se pasarán a las funciones de búsqueda personalizada.

$ python3 codecs_register.py

UTF-8: <codecs.CodecInfo object for encoding utf-8 at
0x1007773a8>
search1: Searching for: no-such-encoding
search2: Searching for: no-such-encoding
ERROR: unknown encoding: no-such-encoding

La instancia CodecInfo devuelta por la función de búsqueda la dice a codecs cómo codificar y decodificar usando todos los diferentes mecanismos soportados: sin estado, incremental, y de flujo. codecs incluye clases base para ayudar a configurar una codificación de mapa de caracteres. Este ejemplo pone todas las piezas juntas para registrar una función de búsqueda que devuelve una instancia de CodecInfo configurada para el codec 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, final=False):
        data, nbytes = codecs.charmap_encode(input,
                                             self.errors,
                                             encoding_map)
        return data


class InvertCapsIncrementalDecoder(codecs.IncrementalDecoder):
    def decode(self, input, final=False):
        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',
            encode=InvertCapsCodec().encode,
            decode=InvertCapsCodec().decode,
            incrementalencoder=InvertCapsIncrementalEncoder,
            incrementaldecoder=InvertCapsIncrementalDecoder,
            streamreader=InvertCapsStreamReader,
            streamwriter=InvertCapsStreamWriter,
        )
    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]), 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))

La clase base del codificador/decodificador sin estado es Codec. Anula encode() y decode() con la nueva implementación (en este caso, llamando a charmap_encode() y charmap_decode() respectivamente). Cada método debe devolver una tupla que contiene los datos transformados y el número de los bytes o caracteres de entrada consumidos. Convenientemente, charmap_encode() y charmap_decode() ya devuelve esa información.

IncrementalEncoder y IncrementalDecoder sirven como clases base para las interfaces incrementales. Los métodos encode() y decode() de las clases incrementales se definen de tal forma que solo devuelven los datos transformados realmente. Cualquier información sobre el almacenamiento en bufer se mantiene como estado interno. La codificación invertcaps no necesita almacenar datos en bufer (usa un mapa de uno a uno). Para codificaciones que producen una cantidad diferente de salida dependiendo de los datos que se procesan, como los algoritmos de compresión, BufferedIncrementalEncoder y BufferedIncrementalDecoder son clases base más apropiadas, ya que gestionan la parte no procesada de la entrada.

StreamReader y StreamWriter necesitan métodos encode() y decode(), también, y como se espera que regresen el mismo valor que la versión de Codec la herencia múltiple puede ser utilizada para la implementación.

$ 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'

Ver también

  • Standard library documentation for codecs
  • locale – Acceder y gestionar los ajustes de configuración y comportamientos basados en localización.
  • io – El módulo io incluye envoltorios de archivos y flujos que manejan codificación y decodificación también.
  • socketserver – Para un ejemplo más detallado de un servidor de repetición, ve el modulo socketserver.
  • encodings –Paquete en la biblioteca estándar que contiene las implementaciones de codificador/decodificador proporcionadas por Python.
  • PEP 100 – PEP de integración de Unicode en Python.
  • Unicode HOWTO – La guía oficial para usar Unicode con Python.
  • Text vs. Data en lugar de Unicode vs. 8-bit – Sección del artículo «Novedades» para Python 3.0 que cubre los cambios enel manejo del texto.
  • Objetos Unicode en Python – Artículo de Fredrik Lundh sobre el uso de conjuntos de caracteres no ASCII en Python 2.0.
  • Cómo usar UTF-8 con Python – Guía rápida de Evan Jones para trabajar con Unicode, incluyendo datos XML y el marcador de orden de bytes.
  • Sobre la bondad de Unicode – Introducción a la internacionalización y Unicode por Tim Bray.
  • Sobre las cadenas de caracteres – Una mirada a la historia del procesamiento de cadenas en lenguajes de programación, por Tim Bray.
  • Caracters vs. Bytes. – Primera parte del «ensayo sobre el procesamiento moderno de cadenas de caracteres para programadores de computadoras». Esta entrega cubre la representación en memoria de texto en formatos distintos de bytes ASCI.
  • Endianness – Explicación de endianness en Wikipedia.
  • Definiciones de la entidad XML del W3C para caracteres – Especificación para las representaciones XML de referencias de caracteres que no pueden ser representadas en una codificación.