codecs - Codifica e Decodifica di Stringhe
Scopo: Codificatori e decodificatori per la conversione di testo tra diverse rappresentazioni
Il modulo codecs fornisce flussi e interfacce file per trascodificare dati. E' più comunemente usato per lavorare con il testo Unicode, ma sono anche disponibili altre codifiche per altri scopi.
Elementi di Unicode
CPython 3.x distingue tra testo e stringhe di byte. Istanze bytes
usano una sequenza di valori di 8 bit mentre le stringhe str
sono gestite internamente come sequenza di code point (un valore numerico compreso nell'intervallo dei valori numerici disponibili per la codifica dei caratteri - n.d.t). I valori dei code point sono salvati come sequenza di 2 o 4 byte ciascuno, a seconda delle opzioni fornite quando Python è stato compilato.
Quando vengono stampati valori str
, essi sono codificati usando uno dei parecchi schemi standard in modo che la sequenza di byte possa essere ricostruita corrispondendo alla stessa stringa di testo successivamente. I byte del valore codificato non sono necessariamente gli stessi dei code point, e la codifica definisce un modo per trascodificare i due insiemi di valori. La lettura di valori Unicode richiede anche la conoscenza della codifica in modo che i byte in arrivo possano essere convertiti nella rappresentazione interna usata dalla classe unicode
.
Le codifiche più comuni per i linguaggi occidentali sono UTF-8
e UTF-16
, le quali usano rispettivamente sequenze di valori di uno o due byte per rappresentare ciascun code point. Altre codifiche possono essere più efficaci per conservare linguaggi dove la maggior parte dei caratteri sono rappresentati da code point che non possono essere compresi in due byte.
Codifiche
Il modo migliore per capire le codifiche è osservare le diverse serie di byte prodotte codificando la stessa stringa in modi diversi. Gli esempi seguenti usano questa funzione per formattare i byte della stringa per facilitarne la lettura.
# codecs_to_hex.py
import binascii
def to_hex(t, nbytes):
"""Formattazione del testo t come sequenza di valori lunghi nbyte
separati da spazi
"""
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 funzione usa binascii
per ottenere la rappresentazione esadecimale dei byte della stringa in input, poi viene inserito uno spazio tra ogni nbytes
byte prima di ritornare il valore.
$ python3 codecs_to_hex.py b'61 62 63 64 65 66' b'6162 6364 6566'
Il primo esempio di codifica inizia stampando il testo 'français'
usando la rappresentazione raw della classe unicode
, seguito dal nome di ciascun carattere dal database Unicode. Le successive due righe codificano la stringa rispettivamente in UTF-8
e UTF-16
e mostra i valori esadecimali risultanti dalla codifica.
# 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)))
Il risultato della codifica di un oggetto str
è un oggetto 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'
Data una sequenza di byte codificati come istanza di bytes
, il metodo decode()
li trascodifica in code point e ritorna la sequenza come istanza di 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('Originali :', repr(text))
print('Codificati :', to_hex(encoded, 1), type(encoded))
print('Decodificati:', repr(decoded), type(decoded))
La scelta della codifica usata non cambia il tipo di risultato.
$ python3 codecs_decode.py Originali : 'français' Codificati : b'66 72 61 6e c3 a7 61 69 73' <class 'bytes'> Decodificati: 'français' <class 'str'>
Lavorare con i file
Codificare e decodificare stringhe è particolarmente importante quando si ha a che fare con operazioni di I/O. A prescindere dal dover scrivere su un file, un socket od un altro canale, i dati devono usare l'opportuna codifica. In generale tutti i dati di testo devono essere decodificati dalla propria rappresentazione in byte in fase di lettura e codificati dai propri valori interni verso una rappresentazione specifica in fase di scrittura. Un programma può esplicitamente codificare e decodificare dati, ma a seconda della codifica usata può essere non triviale determinare se sono stati letti byte a sufficienza per decodificare interamente i dati. codecs fornisce classi per gestire la codifica e la decodifica dei dati, in modo che le applicazioni non debbano compiere questo lavoro.
L'interfaccia più semplice fornita da codecs è un'alternativa alla funzione built-in open()
. La nuova versione funziona proprio come quella built-in, ma aggiunge due nuovi argomenti per specificare la codifica e la tecnica di gestione errori desiderata.
# codecs_open_write.py
from codecs_to_hex import to_hex
import codecs
import sys
encoding = sys.argv[1]
filename = encoding + '.txt'
print('Scrittura verso', filename)
with codecs.open(filename, mode='w', encoding=encoding) as f:
f.write('français')
# Determina il raggruppamento di byte da usare per to_hex()
nbytes = {
'utf-8': 1,
'utf-16': 2,
'utf-32': 4,
}.get(encoding, 1)
# Mostra i byte raw nel file
print('Contenuto del file:')
with open(filename, mode='rb') as f:
print(to_hex(f.read(), nbytes))
Questo esempio inizia con una stringa unicode
con "ç" e salva il testo in un file usando una codifica specificata da riga di comando.
$ python3 codecs_open_write.py Traceback (most recent call last): File "codecs_open_write.py", line 8, in <module> encoding = sys.argv[1] IndexError: list index out of range
Leggere i dati con open()
è lineare, con una avvertenza: la codifica deve essere conosciuta in anticipo, per poter impostare il decodificatore correttamente. Alcuni formati di dati tipo XML, specificano la codifica come parte del file, ma in genere spetta all'applicazione questa gestione. codecs riceve semplicemente la codifica come argomento ed assume che sia corretta.
# codecs_open_read.py
import codecs
import sys
encoding = sys.argv[1]
filename = encoding + '.txt'
print('Lettura da', filename)
with codecs.open(filename, mode='r', encoding=encoding) as f:
print(repr(f.read()))
Questo esempio legge i file creati dall'esempio precedente, e stampa alla console la rappresentazione dell'oggetto unicode
risultante.
$ python3 codecs_open_read.py Traceback (most recent call last): File "codecs_open_read.py", line 6, in <module> encoding = sys.argv[1] IndexError: list index out of range
Ordine dei Byte
Le codifiche multi-byte tipo UTF-16 ed UTF-32 pongono un problema quando si devono trasferire dati da diversi sistemi, sia copiando direttamente il file che tramite comunicazione di rete. Sistemi diversi utilizzano ordinamenti diversi dei byte alti e bassi. Questa caratteristica dei dati, nota come endianness dipende da fattori tipo l'architettura hardware e le scelta fatte dal sistema operativo e dagli sviluppatori dell'applicazione. Non sempre è noto in anticipo quale ordine di byte usare per un certo insieme di dati, quindi le codifiche multi-byte includono un marcatore di ordine di byte (BOM) come primi byte dell'output codificato. Ad esempio, UTF-16 è definito in modo tale che 0xFFFE e 0xFEFF non sono caratteri validi, e possono essere usati per indicare l'ordine di byte. codecs definisce costanti per i marcatori BOM usati da UTF-16 ed 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
e BOM_UTF32
sono automaticamente impostati per utilizzare gli appropriati valori big-endian o little-endian a seconda dell'ordine dei byte nativo del sistema operativo.
$ 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'
L'ordinamento dei byte viene rilevato e gestito automaticamente dai decodificatori in codecs, ma può essere specificato un ordinamento esplicito nella codifica.
# 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('Ordina nativo :', to_hex(codecs.BOM_UTF16, 2))
print('Ordine selezionato:', to_hex(bom, 2))
# Codifica il testo
encoded_text = 'français'.encode(encoding)
print('{:14}: {}'.format(encoding, to_hex(encoded_text, 2)))
with open('nonnative-encoded.txt', mode='wb') as f:
# Scrive il byte-order marker selezionato. Non e' incluso nel
# testo codificato in quanto la cosa e' stata resa esplicita in fase di
# selezione della codifica
f.write(bom)
# Scrive la stringa di byte per il testo codificato.
f.write(encoded_text)
codecs_bom_create_file.py
è in grado di rilevare l'ordinamento dei byte nativo, poi usa la forma alternativa esplicita in modo che l'esempio successivo possa dimostrare l'auto rilevamento durante la lettura.
$ python3 codecs_bom_create_file.py Ordina nativo : b'fffe' Ordine selezionato: b'feff' utf_16_be : b'0066 0072 0061 006e 00e7 0061 0069 0073'
codecs_bom_create_detection.py
non specifica un ordine di byte all'apertura del file, quindi il decodificatore usa il valore BOM nei primi due byte del file per determinarlo.
# codecs_bom_detection.py
import codecs
from codecs_to_hex import to_hex
# Lettura dei dati grezzi
with open('nonnative-encoded.txt', mode='rb') as f:
raw_bytes = f.read()
print('Grezzo :', to_hex(raw_bytes, 2))
# Riapertura del file lasciando che codecs identifichi il BOM
with codecs.open('nonnative-encoded.txt',
mode='r',
encoding='utf-16',
) as f:
decoded_text = f.read()
print('Decodificato:', repr(decoded_text))
Visto che i primi due byte del file sono usati per il rilevamento dell'ordine dei byte, non sono inclusi nei dati ritornati da read()
.
$ python3 codecs_bom_detection.py Grezzo : b'feff 0066 0072 0061 006e 00e7 0061 0069 0073' Decodificato: 'français'
Gestione degli Errori
La sezione precedente evidenziava la necessità di sapere in anticipo la codifica utilizzata durante la lettura e scrittura di file Unicode. Impostare correttamente la codifica è importante per due ragioni. Se la codifica è configurata nel modo sbagliato durante la lettura di un file, i dati saranno male interpretati a potrebbero corrompersi o semplicemente fallirebbe la codifica. Non tutti i caratteri Unicode possono essere rappresentati in utte le codifiche; alla stessa stregua una errata codifica in scrittura potrebbe generare errori e perdita di dati.
codecs usa le stesse cinque opzioni di gestione errore fornite dai metodi encode() di str e decode() di bytes, elencati di seguito:
La condizione di errore più comune è ricevere un errore UnicodeEncodeError
quando si scrivono dati Unicode in un canale di uscita ASCII, tipo un normale file oppure
# codecs_encode_error.py
import codecs
import sys
error_handling = sys.argv[1]
text = 'français'
try:
# Salva i dati, codificati come ASCII, usando la modalità di gestione
# errori specificata da riga di comando
with codecs.open('encode_error.txt', 'w',
encoding='ascii',
errors=error_handling) as f:
f.write(text)
except UnicodeEncodeError as err:
print('ERRORE:', err)
else:
# Se non ci sono errori nella scrittura al file
# mostra il contenuto
with open('encode_error.txt', 'rb') as f:
print('Contenuto del file: {!r}'.format(f.read()))
Sebbene la modalità strict
sia la più sicura per assicurarsi che un'applicazione imposti esplicitamente la codifica corretta per tutte le operazioni di I/O, potrebbe condurre a blocchi dei programmi quando viene sollevata una eccezione.
$ python3 codecs_encode_error.py Traceback (most recent call last): File "codecs_encode_error.py", line 6, in <module> error_handling = sys.argv[1] IndexError: list index out of range
Alcune delle altre modalità di gestione errore sono più flessibili. Ad esempio replace
assicura che nessuna eccezione viene sollevata, a scapito di possibili perdite dei dati che non possono essere convertiti nella codifica richiesta. Il carattere Unicode per la cediglia non può essere codificato in ASCII, ma viene sostituito nel risultato da un ?
senza generare errori.
$ codecs_encode_error.py replace /dati/dev/python/pymotw3restyling/dumpscripts/_pyrunner.sh: line 3: codecs_encode_error.py: command not found
Per non avere interamente a che fare con problemi di dati si usi ignore
. Tutti i dati che non possono essere codificati sono ignorati.
$ python3 codecs_encode_error.py ignore Contenuto del file: b'franais'
Ci sono due opzioni di gestione errori senza perdita di dati, entrambe prevedono la sostituzione del carattere con una sua rappresentazione alternativa definita da uno standard a parte rispetto alla codifica, xmlcharrefreplace
utilizza un riferimento di carattere XML come sostituto (la lista dei caratteri è specificata nel documento W3C XML Entity Definitions for Characters).
$ python3 codecs_encode_error.py xmlcharrefreplace Contenuto del file: b'français'
L'altro schema di gestione errore senza perdita di dati è bachslashreplace
che produce un risultato in uscita come il valore ritornato chiamando repr()
su di un oggetto unicode
. I caratteri Unicode sono sostituiti da \u seguito dal valore esadecimale del code point.
$ python3 codecs_encode_error.py backslashreplace Contenuto del file: b'fran\\xe7ais'
Errori di Decodifica
E' possibile incorrere in errori durante la decodifica dei dati, specialmente se è usata la codifica sbagliata.
# codecs_decode_error.py
import codecs
import sys
from codecs_to_hex import to_hex
error_handling = sys.argv[1]
text = "français"
print("Originale :", repr(text))
# Salva i dati con una codifica
with codecs.open("decode_error.txt", "w", encoding="utf-16") as f:
f.write(text)
# Scarica i dati dal file
with open("decode_error.txt", "rb") as f:
print("Contenuto del file:", to_hex(f.read(), 1))
# Tenta di leggere i dati con la codifica errata
with codecs.open("decode_error.txt", "r", encoding="utf-8",
errors=error_handling) as f:
try:
data = f.read()
except UnicodeDecodeError as err:
print("ERRORE:", err)
else:
print("Letti :", repr(data))
Come per la codifica, la modalità di gestione errore strict
solleva una eccezione se il flusso di byte non può essere decodificato propriamente. In questo caso un UnicodeDecodeError
viene generato tentando di convertire parte del BOM UTF-16 in un carattere usando il decodificatore UTF-8.
$ python3 codecs_decode_error.py strict Originale : 'français' Contenuto del file: b'ff fe 66 00 72 00 61 00 6e 00 e7 00 61 00 69 00 73 00' ERRORE: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
Passare alla modalità ignore
fa sì che il decodificatore ignori i byte non validi. Il risultato comunque non è ancora quello atteso, visto che vengono inclusi byte nulli.
$ python3 codecs_decode_error.py replace Originale : 'français' Contenuto del file: b'ff fe 66 00 72 00 61 00 6e 00 e7 00 61 00 69 00 73 00' Letti : '��f\x00r\x00a\x00n\x00�\x00a\x00i\x00s\x00'
Traduzioni in Codifica
Sebbene la maggior parte delle applicazioni internamente lavora con dati str
, la decodifica o codifica è parte di una operazione di I/O; ci sono volte nelle quali modificando la codifica di un file senza passare per quel formato dati intermedio è utile. EncodedFile()
riceve un file handle di apertura usando una codifica e lo incapsula in una classe che trascodifica i dati in un'altra codifica quando si verifica una operazione I/O.
# codecs_encodedfile.py
from codecs_to_hex import to_hex
import codecs
import io
# Versione grezza dei dati originali
data = 'français'
# Codifica manuale come UTF-8
utf8 = data.encode('utf-8')
print('Parte com UTF-8 :', to_hex(utf8, 1))
# Imposta un buffer in uscita, quindi lo incapsula come EncodedFile.
output = io.BytesIO()
encoded_file = codecs.EncodedFile(output, data_encoding='utf-8',
file_encoding='utf-16')
encoded_file.write(utf8)
# Recupera il contenuto del buffer con codifica della stringa
# di byte in UTF-16
utf16 = output.getvalue()
print('Codificato in UTF-16:', to_hex(utf16, 2))
# Imposta un altro buffer con i dati UTF-16 per la lettura
# e li incapsula con un altro EncodedFile.
buffer = io.BytesIO(utf16)
encoded_file = codecs.EncodedFile(buffer, data_encoding='utf-8',
file_encoding='utf-16')
# Legge la versione dati codificata come UTF-8
recoded = encoded_file.read()
print('Ritorna a UTF-8 :', to_hex(recoded, 1))
Codifiche Non Unicode
Sebbene la maggior parte degli esempi precedenti utilizza codifiche Unicode, codecs può essere usato per molti altri tipi di trascodifiche. Ad esempio Python comprende codec per lavorare con i formati dato base-64, bzip2, ROT-13, ZIP e altri.
# codecs_rot13.py
import codecs
import io
buffer = io.StringIO()
stream = codecs.getwriter('rot_13')(buffer)
text = 'abcdefghijklmnopqrstuvwxyz'
stream.write(text)
stream.flush()
print('Originale:', text)
print('ROT-13 :', buffer.getvalue())
Qualsiasi trasformazione che possa essere espressa come una funzione che riceve un singolo argomento e ritorna una stringa Unicode o byte può essere registrata come un codec. Ad esempio per il codec 'rot_13
', l'input dovrebbe essere una stringa Unicode e l'output sarà anch'esso una stringa Unicode.
$ python3 codecs_rot13.py Originale: abcdefghijklmnopqrstuvwxyz ROT-13 : nopqrstuvwxyzabcdefghijklm
Utilizzando codecs per impacchettare un flusso di dati si ha a disposizione una interfaccia più semplice che lavorare direttamente 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('Lunghezza originale:', len(text))
compressed_data = buffer.getvalue()
print('ZIP compresso :', len(compressed_data))
buffer = io.BytesIO(compressed_data)
stream = codecs.getreader('zlib')(buffer)
first_line = stream.readline()
print('Legge la prima riga:', repr(first_line))
uncompressed_data = first_line + stream.read()
print('Decompressi :', len(uncompressed_data))
print('Uguali :', text == uncompressed_data)
Non tutti i tipi di compressione o sistemi di codifica supportano la lettura di una porzione di dati attraverso l'interfaccia del flusso usando readline()
oppure read()
visto che occorre trovare la fine di un segmento compresso per espanderlo. Se un programma può contenere l'intero insieme di dati non compressi in memoria, si utilizzino le caratteristiche di accesso incrementale della libreria di compressione invece di codecs.
$ python3 codecs_zlib.py Lunghezza originale: 1350 ZIP compresso : 48 Legge la prima riga: b'abcdefghijklmnopqrstuvwxyz\n' Decompressi : 1350 Uguali : True
Codifica Incrementale
Alcune delle codifiche fornite, specialmente bz2
e zlib
potrebbero modificare considerevolmente la lunghezza del flusso di dati durante la lavorazione. Per insiemi di dati molto grandi, queste codifiche operano meglio in modalità incrementale, lavorando su piccole porzioni di dati alla volta. Le API IncrementalEncoder
e IncrementalDecoder
sono progettate a questo scopo.
# codecs_incremental_bz2.py
import codecs
import sys
from codecs_to_hex import to_hex
text = b'abcdefghijklmnopqrstuvwxyz\n'
repetitions = 50
print('Lung. testo :', len(text))
print('Ripetizioni :', repetitions)
print('Lung. prevista:', len(text) * repetitions)
# Codifica il testo parecchie volte sviluppando un grande ammontare di dati
encoder = codecs.getincrementalencoder('bz2')()
encoded = []
print()
print('In codifica:', end=' ')
last = repetitions - 1
for i in range(repetitions):
en_c = encoder.encode(text, final=(i == last))
if en_c:
print('\nCodificati : {} byte'.format(len(en_c)))
encoded.append(en_c)
else:
sys.stdout.write('.')
all_encoded = b''.join(encoded)
print()
print('Lunghezza totale codificati:', len(all_encoded))
print()
# Decodifica la stringa di byte un byte alla volta
decoder = codecs.getincrementaldecoder('bz2')()
decoded = []
print('In decodifica:', end=' ')
for i, b in enumerate(all_encoded):
final = (i + 1) == len(text)
c = decoder.decode(bytes([b]), final)
if c:
print('\nDecodificati : {} caratteri'.format(len(c)))
print('In decodifica:', end=' ')
decoded.append(c)
else:
sys.stdout.write('.')
print()
restored = b''.join(decoded)
print()
print('Lunghezza totale non compressi:', len(restored))
Ogni volta che dati sono passati al codificatore o decodificatore viene aggiornato il proprio stato interno. Quando lo stato interno è consistente (così come definito dal codec), i dati sono restituiti e lo stato viene reimpostato. Fino a quel punto le chiamate a encode()
o decode()
non ritorneranno alcun dato. Quando l'ultimo bit di dati è stato ricevuto, l'argomento final
dovrebbe essere impostato a True
in modo che il codec sappia come far fuoriuscire i dati rimasti nel proprio buffer.
$ python3 codecs_incremental_bz2.py Lung. testo : 27 Ripetizioni : 50 Lung. prevista: 1350 In codifica: ................................................. Codificati : 99 byte Lunghezza totale codificati: 99 In decodifica: ........................................................................................ Decodificati : 1350 caratteri In decodifica: .......... Lunghezza totale non compressi: 1350
Dati Unicode e la Comunicazione in Rete
I socket di rete sono flussi di byte, e a differenza dei flussi standard in input ed output non supportano òa codifica nella modalità predefinita. Questo significa che i programmi che vogliono inviare o ricevere dati Unicode tramite una rete devono codificarli in byte prima di scriverli in un socket. Questo server ripete i dati che riceve al mittente.
# codecs_socket_fail.py
import sys
import socketserver
class Echo(socketserver.BaseRequestHandler):
def handle(self):
# Si ottengono alcuni byte e li riverbero al client.
data = self.request.recv(1024)
self.request.send(data)
return
if __name__ == '__main__':
import codecs
import socket
import threading
address = ('localhost', 0) # lasciamo assegnare la porta al kernel
server = socketserver.TCPServer(address, Echo)
ip, port = server.server_address # quale porta è stata assegnata?
t = threading.Thread(target=server.serve_forever)
t.setDaemon(True) # non lasciamolo appeso all'uscita
t.start()
# Connessione al server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
# Invio dati
# ERRORE!: prima non sono stati codificati!
text = 'français'
len_sent = s.send(text)
# Recezione di una risposta
response = s.recv(len_sent)
print(repr(response))
# Pulizia
s.close()
server.socket.close()
Si sarebbe potuto codificare esplicitamente i dati prima di ogni chiamata a send()
, ma la mancanza di una chiamata a send()
avrebbe generato un errore di codifica.
$ python3 codecs_socket_fail.py Traceback (most recent call last): File "codecs_socket_fail.py", line 36, in <module> len_sent = s.send(text) TypeError: a bytes-like object is required, not 'str'
Si usi makefile()
per ottenere un handle di tipo file per il socket, quindi lo si impacchetti in un lettore o scrittore basato su flusso, il che fa sì che le stringhe Unicode siano codificate durante l'entrata e l'uscita dal socket.
# codecs_socket.py
import sys
import socketserver
class Echo(socketserver.BaseRequestHandler):
def handle(self):
"""Si ottengono alcuni byte e li si riverbera al client
Non occorre decoficarli visto che non sono usati.
"""
data = self.request.recv(1024)
self.request.send(data)
class PassThrough:
def __init__(self, other):
self.other = other
def write(self, data):
print('In scrittura:', repr(data))
return self.other.write(data)
def read(self, size=-1):
print('In lettura :', 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) # Lasciamo che il kernel ci fornisca una porta
server = socketserver.TCPServer(address, Echo)
ip, port = server.server_address # quale porta ci è stata assegnata?
t = threading.Thread(target=server.serve_forever)
t.setDaemon(True) # don't hang on exit
t.start()
# Connessione al server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
# Impacchetta il socket con un lettore e scrittore
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))
# Invio dati
text = 'français'
print('In invio :', repr(text))
outgoing.write(text)
outgoing.flush()
# Receive a response
response = incoming.read()
print('Ricevuti:', repr(response))
# Pulizia
s.close()
server.socket.close()
Questo esempio usa PassThrough
per mostrare come i dati siano codificati prima di essere inviati, e come la risposta sia decodificata dopo che è stata ricevuta nel client.
$ python3 codecs_socket.py In invio : 'français' In scrittura: b'fran\xc3\xa7ais' In lettura : b'fran\xc3\xa7ais' In lettura : b'' Ricevuti: 'français'
Definire un Codec Personalizzato
Visto che Python è già fornito id un gran numero di codecs standard, è improbabile che un'applicazione debba definire un codificatore o decodificatore personalizzato. Quando è necessario, tuttavia, ci sono parecchie classi base in codecs che facilitano il processo.
Il primo passo è di capire la natura della trasformazione descritta dal codificatore. Questi esempi usano una codifica "inverticaps", che converte le lettere maiuscole in minuscole e viceversa. Ecco una semplice definizione di una funzione di codifica che esegue questa trasformazione data una stringa in input.
# codecs_invertcaps.py
import string
def invertcaps(text):
"""Ritorna una nuova stringa con le maiuscole in minuscole e viceversa
"""
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'))
In questo caso il codificatore ed il decodificatore sono la stessa funzione (così come nel caso con ROT-13
).
$ python3 codecs_invertcaps.py abcDEF ABCdef
Sebbene sia facile da comprendere, questa implementazione non è efficiente, specialmente per stringhe di testo molto grandi. Fortunatamente codecs comprende alcune funzioni di convenienza per creare codecs basati su mappe di caratteri tipo inverticaps. Una codifica di un mappa di caratteri è composta da due dizionari. La mappa di codifica converte i valori dei caratteri dalla stringa in input in valori byte in uscita e la mappa di decodifica funziona all'opposto. Prima si crea la mappa di decodifica, pui si usa make_encoding_map()
per converirla in una mappa di codifica. Le funzioni C charmap_encode()
e charmap_decode()
usano le mappe per convertire i propri dati in input efficientemente.
# codecs_invertcaps_charmap.py
import codecs
import string
# Mappa ogni caratter a se stesso
decoding_map = codecs.make_identity_dict(range(256))
# Fa una lista di coppie di valori ordinali per le
# lettere minuscole e maiuscole
pairs = list(zip(
[ord(c) for c in string.ascii_lowercase],
[ord(c) for c in string.ascii_uppercase],
))
# Modifica la mappatura per convertire le maiuscole in minuscole e viceversa
decoding_map.update({
upper: lower
for (lower, upper)
in pairs
})
decoding_map.update({
lower: upper
for (lower, upper)
in pairs
})
# Crea una mappa di codifica separata
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)
Sebbene le mappe di codifica e decodifica a per inverticaps siano le stesse, potrebbe non essere sempre il caso. make_encoding_map()
rileva situazioni dove più di un carattere in input è codificato con lo stesso byte in output e sostituisce il valore di codifica con None
per marcare la codifica come indefinita.
$ python3 codecs_invertcaps_charmap.py (b'ABCdef', 6) ('ABCdef', 6) True
La mappa di caratteri di codifica e decodifica supporta tutti i metodi standard di gestione errori descritti in precedenza, quindi non è necessario lavoro supplementare per conformarsi a quella parte dell'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))
Visto che il code point Unicode per il pi greco non è nella mappa di codifica, la modalità di gestione dell'errore strict solleva una eccezione.
$ 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>
Dopo che le mappe di codifica e decodifica sono state definite, occorre impostare qualche altra classe, e la codifica dovrebbe essere registrata. register()
aggiunge una funzione di ricerca al registro in modo che quando un utente vuole usare la codifica, codecs può localizzarla. La funzione di ricerca deve ricevere una singola stringa come argomento con il nome della codifica, e ritornare un oggetto CodecInfo
se la codifica è nota, oppure None
in caso contrario.
# codecs_register.py
import codecs
import encodings
def search1(encoding):
print('ricerca1: Ricerca di:', encoding)
return None
def search2(encoding):
print('search2: Ricerca di:', encoding)
return None
codecs.register(search1)
codecs.register(search2)
utf8 = codecs.lookup('utf-8')
print('UTF-8:', utf8)
try:
unknown = codecs.lookup('codifica-non-presente')
except LookupError as err:
print('ERRORE:', err)
Possono essere registrate multiple funzioni di ricerca, ciascuna della quali sarà chiamata a turno fino a che una ritorna un CodecInfo
oppure la lista viene esaurita. La funzione di ricerca interna registrata da codecs sa come caricare i codec standard, tipo UTF-8 da encodings
, in modo che questi nomi non saranno mai passati alle funzioni di ricerca personalizzate.
$ python3 codecs_register.py UTF-8: <codecs.CodecInfo object for encoding utf-8 at 0x7f10cc6549a0> ricerca1: Ricerca di: codifica-non-presente search2: Ricerca di: codifica-non-presente ERRORE: unknown encoding: codifica-non-presente
L'istanza di CodecInfo
ritornata dalla funzione di ricerca dice a codecs come codificare e decodificare usando tutti i diversi meccanismi supportati: stateless, incrementali e stream. codecs include classi base per aiutare nell'impostazione di una mappa di caratteri di codifica. Questo esempio mette insieme tutti i pezzi per registrare una funzione di ricerca che ritorna una istanza di CodecInfo
configurata per il codec inverticaps.
# 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):
"""Ritorna il codec per '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('Codificati "{}" in "{}", consumando {} caratteri'.format(
text, encoded_text, consumed))
# Scrittore di flusso
import io
buffer = io.BytesIO()
writer = codecs.getwriter('invertcaps')(buffer)
print('StreamWriter per il buffer io: ')
print(' scrittura di "abcDEF"')
writer.write('abcDEF')
print(' contenuto del buffer: ', 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 convertito {!r} a {!r}'.format(
encoded_text, decoded_text))
La classe base per il codificatore/decodificatore stateless è Codec
. Si sovrascrivono encode() e
decode()
con una nuova implementazione (in questo caso, chiamando charmap_encode()
e charmap_decode()
rispettivamente). Ciascun metodo deve ritornare una tupla che contiene i dati trasformati e il numero di byte in input o caratteri consumati. Convenientemente, charmap_encode()
e charmap_decode()
ritornano già quelle informazioni.
IncrementalEncoder
e IncrementalDecoder
servono come classi base per le interfacce incrementali. I metodi encode()
e decode()
delle classi incrementali sono definiti in modo tale che ritornano solo i dati effettivamente trasformati. Ogni informazione circa la gestione del buffer viene mantenuta come stato interno. La codifica inverticaps non necessita di gestire un buffer di dati (usa una mappatura uno-a-uno). Per codifiche che producono un diverso ammontare di output a seconda dei dati in elaborazione, tipo gli algoritmi di compressione, BufferedIncrementalEncoder
e BufferedIncrementalDecoder
sono classi base più appropriate, visto che gestiscono la porzione non elaborata dell'input.
Anche a StreamReader
e StreamWriter
servono i metodi encode()
e decode()
, visto che ci si attende che ritornino gli stessi valori della versione da Codec
è possibile usare l'ereditarietà multipla per questa implementazione.
$ python3 codecs_invertcaps_register.py Codificati "abcDEF" in "b'ABCdef'", consumando 6 caratteri StreamWriter per il buffer io: scrittura di "abcDEF" contenuto del buffer: b'ABCdef' IncrementalDecoder convertito b'ABCdef' a 'abcDEF'
Vedere anche:
- codecs
- La documentazione della libreria standard per questo modulo
- locale
- Altri strumenti di localizzazione
- socketserver
- Per un esempio più dettagliato di un server che ripete quanto ricevuto si veda il modulo socketserver
- PEP 100
- PEP per l'integrazione di Unicode in Python
- Unicode HOWTO
- La guida ufficiale per l'utilizzo di Unicode con Python
- Text vs. Data instead of Unicode vs. 8-bit
- Articolo per Python 3.0 nella sezione "What's new" che tratta le modifiche nella gestione del testo.
- Python Unicode Objects
- Articolo di Fredrik Lundh circa l'uso di insiemi di caratteri non ASCII in Python 2.0.
- How to Use UTF-8 with Python
- Veloce guida di Evan Jones per lavorare con Unicode, inclusi dati XML e Byte-Order Marker.
- On the Goodness of Unicode
- Introduzione alla internazionalizzazione ed Unicode di Tim Bray.
- On Character Strings
- Uno sguardo alla storia dell'elaborazione di stringhe nei linguaggi di programmazione, di Tim Bray.
- Characters vs. Bytes
- Prima parte del saggio "essay on modern character string processing for computer programmers". Tratta la rappresentazione in memoria del testi in formati diversi dai byte ASCII.