Dati binari

 ☗ 

Dati binari

Nei seguenti casi si devono trattare i propri dati come sequenze di byte:

Le sequenze di byte sono rappresentate con stringhe binarie in Python2 e oggetti bytes in Python3. Ogni byte della sequenza può assumere un valore compreso tra 0 e 255.

Per preparare dei dati da trasmettere/scrivere, ad esempio numeri interi (sempre compresi tra 0 e 255) contenuti in una lista, o riottenere una lista di interi dai dati ricevuti/letti, si possono usare le seguenti sintassi:

seqBin = struct.pack("B"*len(lista), *lista)            # Py2 & Py3  
seqBin = "".join(chr(n) for n in lista)                 # Py2
seqBin = bytes(lista)                                   # Py3
lista = list(struct.unpack("B"*len(seqBin), seqBin))    # Py2 & Py3 
lista = [ord(ch) for ch in seqBin]                      # Py2 
lista = list(seqBin)                                    # Py3 

Stesse operazioni ma per preparare/riottenere un singolo valore:

datoBin = struct.pack("B", valore)      # Py2 & Py3  
datoBin = chr(valore)                   # Py2
datoBin = bytes([valore])               # Py3
valore = struct.unpack("B", datoBin)[0] # Py2 & Py3 
valore = ord(datoBin)                   # Py2 
valore = datoBin[0]                     # Py3 

Le funzioni pack e unpack del modulo struct sono lente, ma accettano e restituiscono automaticamente il tipo di oggetto adatto alla versione di Python utilizzata, e permettono di trasformare in sequenze di byte anche tipi di dati più complessi, come numeri interi con valori maggiori di 255 e numeri float. Per queste caratteristiche vedere la documentazione del modulo struct.

È anche possibile specificare direttamente una sequenza di byte tramite una stringa di valori espressi in esadecimale:

seqBin = "\x0A\xFF\x00\xCF\xC9"       # Py2
seqBin = b"\x0A\xFF\x00\xCF\xC9"      # Py3
seqBin = bytes.fromhex("0AFF00CFC9")  # Py3


Dati testuali

Scrivere/trasmettere o leggere/ricevere una stringa di caratteri testuali richiede sempre di specificare/conoscere quale sia la codifica usata per rappresentare sotto forma numerica questi caratteri (encoding). A seconda dei caratteri e del tipo di codifica, il numero di byte richiesto per codificarli può anche essere maggiore del numero dei caratteri stessi.

Per trasformare una generica stringa di caratteri unicode in una sequenza di byte si possono usare queste sintassi:

seqBin = stringa.encode(ENCODING)  # Py2 & Py3
seqBin = bytes(stringa, ENCODING)  # Py3
Dove ENCODING deve essere una delle codifiche ammesse nella colonna codec di questa tabella.

Invece per riottenere la stringa unicode dai dati ricevuti/letti:
stringa = seqBin.decode(ENCODING)  # Py2 & Py3
stringa = str(seqBin, ENCODING)    # Py3  

NOTA: i codici unicode dei caratteri appartenenti al set ASCII 7 bit, corrispondono esattamente ai codici ASCII dei corrispondenti caratteri, questo vuol dire che se una stringa è composta di soli caratteri ASCII, codificarla in ASCII o in utf-8 produce lo stesso risultato, e ogni carattere occupa un solo byte.



La doppia faccia delle stringhe Python2

Una stringa di caratteri testuali dovrebbe sempre essere gestita come sequenza di codici unicode, in modo da poter rappresentare tutti i caratteri di qualsiasi lingua, oltre numerosi altri simboli matematici, finanziari e grafici (le funzioni chr e ord in questo caso accettano e restituiscono il codice unicode universale del carattere visibile in questa tabella).

In Python3 le stringhe di caratteri sono per default solo unicode:

stringa = "€©€"

mentre in Python2 vanno appositamente definite tali anteponendo 'u':

stringa = u"€©€"

Per ottenere una sequenza binaria, i cui byte rappresentino i caratteri della stringa unicode codificati secondo una precisa convenzione (encoding), le stringhe vanno codificate (encode), e, viceversa, per ottenere una stringa unicode da una sequenza binaria, la sequenza va decodificata (decode).

La sequenza binaria (stringa binaria in Python2 e oggetto bytes in Python3) può essere più lunga del numero di caratteri originali della stringa unicode, perché alcuni caratteri possono richiedere più di un byte per essere codificati.

In Python2 quindi le stringhe binarie svolgono la funzione svolta dall'oggetto bytes di Python3, e possono contenere non solo la codifica di stringhe unicode, ma anche sequenze arbitrarie di byte che nulla hanno a che vedere con dati testuali.

Nel passaggio da Python2 a Python3 nasce spesso confusione per almeno due motivi, il primo è non avere chiara la differenza tra una stringa di caratteri unicode e una stringa codificata binaria (soprattutto se si sono utilizzati sempre solo caratteri ASCII), il secondo è che la sintassi comune:

stringa = "...."

produce risultati completamente diversi nelle due versioni del linguaggio:

# Python2
s = u"stringa"  # stringa unicode
s = "stringa"   # sequenza binaria (stringa binaria)

# Python3
s = "stringa"   # stringa unicode
s = b"stringa"  # sequenza binaria (oggetto bytes)

Finché si usano esclusivamente caratteri ASCII 7 bit, grossi problemi non nascono: se si definiscono stringhe unicode basta encodarle indifferentemente ASCII o utf-8, mentre se si definiscono direttamente stringhe binarie o oggetti bytes, il contenuto equivale già a quello che si otterrebbe dall'encode delle equivalenti stringhe unicode.

Invece nel caso in cui si volessero usare caratteri non ASCII 7 bit direttamente nelle sequenze binarie, iniziano i problemi. Ad esempio si può notare che la seguente stringa Python2 non è formata da caratteri ma da una loro codifica multibyte semplicemente chiedendone la lunghezza:

stringa = "ßcioè"
print(len(stringa))   # ---> 7

Non la si può convertire in unicode se non si conosce la codifica usata dal proprio sistema, e neppure ricodificare in un altro encoding, perché in mancanza di ulteriori informazioni Python prova prima di tutto a decodificare la stringa utilizzando il codec ascii:

nuovaStringa = stringa.encode("cp437")
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib/python2.7/encodings/cp437.py", line 12, in encode
    return codecs.charmap_encode(input,errors,encoding_map)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: 
ordinal not in range(128)

E non è neppure detto che la sequenza di byte ottenuta su un sistema corrisponda a quella ottenuta su un altro. In sostanza, a parte l'unico caso in cui si utilizzino esclusivamente caratteri ASCII 7 bit, lavorare direttamente con il testo in stringhe codificate Python2 è fonte di guai e incompatibilità.

Ricapitolando, se si vogliono usare caratteri non ASCII nel testo sorgente bisogna sempre avere le seguenti accortezze:

  1. Specificare sulla prima riga del sorgente (o sulla seconda se la prima viene utilizzata per uno shabang):
    # -*- coding: utf-8 -*-
  2. In Python2 definire sempre esplicitamente le stringhe come unicode:
    stringa = u"àèìùò"
  3. Impostare l'editor per salvare il file sorgente con codifica utf-8.