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.
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.
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
.
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
Valores predeterminados de Unicode 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, conector 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.
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.
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.
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
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.
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.
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.
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ç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.
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.
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.
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
.
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.
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 conectores 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 conector. Este servidor repite los datos que recibe de vuelta al remitente.
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 conector,
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 conector.
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.
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.
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.
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.
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.
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
búfer se mantiene como estado interno. La codificación invertcaps no necesita
almacenar datos en búfer (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
- Documentación de la biblioteca estándar para codecs
locale
– Acceder y gestionar los ajustes de configuración y comportamientos basados en localización.io
– El móduloio
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 modulosocketserver
.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.