File binari
Rappresentazione dei dati in forma binaria
Per poter scrivere dati in un file binario è necessario trasformarli in sequenze di byte con esplicitazione del modo in cui queste sequenze sono rappresentate (ad esempio, quanti byte per un numero intero, se il numero è con o senza segno, se l'ordine dei byte è big-endian o little-endian, ecc.).
Il modulo struct
di Python ci viene in soccorso.
Si consideri il seguente esempio:
import struct
FORMAT='<2hf'
data = struct.pack(FORMAT, 30000, -12, 2.35) #packing
items = struct.unpack(FORMAT, data) #unpacking
print(items)
Output:
(30000, -12, 2.3499999046325684)
Il formato FORMAT, da interpretare alla luce delle informazioni presenti nella pagina struct della documentazione, significa:
'<': che l'ordine dei byte è little-endian
'h': che segue un intero corto di due byte
'f': che segue un numero in virgola mobile di quattro byte
Se si ha a che fare con stringhe, bisogna scegliere il tipo di rappresentazione della stringa e gestire la codifica e la decodifica:
name="Mario Rossi Albertucci"
age=20
FORMAT='<20sb'
data = struct.pack(FORMAT, name.encode('UTF-8'), age) #packing
items = struct.unpack(FORMAT, data) #unpacking
print(items[0].decode('UTF-8'), items[1])
Output:
Mario Rossi Albertuc 20
Si noti che in questo caso la stringa viene codificata in formato UTF-8 e poi messa in una stringa di 20 caratteri (per cui il valore viene troncato).
Scrittura di dati in un file binario
Un primo semplice esempio di scrittura di dati in un file binario potrebbe essere il seguente (nel quale abbiamo aggiunto l'uso di un oggetto di tipo struct.Struct
).
import struct
class Person():
struct_format = struct.Struct('<20sb')
def __init__(self, name, age):
self.name = name
self.age = age
def getPacked(self):
return self.struct_format.pack(
self.name.encode('UTF-8'),
self.age
)
people = (
Person('Mario Rossi', 20),
Person('Alberto Alberti', 21),
Person('Barbara Balducci', 22),
Person('Carla Carletti', 23)
)
with open('people.dat', 'wb') as f:
for person in people:
f.write(person.getPacked())
Lettura di dati da un file binario
Per la lettura è sufficiente leggere il file e fare l'unpacking; abbiamo però modificato la funzione __init__()
della classe Person per far sì che la stringa venga compattata, rimuovendo spazi e valori nulli finali che erano stati aggiunti in fase di impacchettamento.
import struct
class Person():
struct_format = struct.Struct('<20sb')
def __init__(self, name, age):
self.name = name.strip('\0x00 ')
self.age = age
def getPacked(self):
return self.struct_format.pack(
self.name.encode('UTF-8'),
self.age
)
def __str__(self):
return 'Nome: %s, età: %d' % (self.name, self.age)
with open('people.dat', 'rb') as f:
while True:
data = f.read(Person.struct_format.size)
if not data:
break
name, age = Person.struct_format.unpack(data)
p = Person(name.decode('UTF-8'), age)
print(p)
Accesso diretto (random access)
Se durante la lettura si vuole posizionare il cursore in un determinato punto del file, si può usare il metodo seek()
.
Ad esempio, supponendo di avere un file people.idx contenente un indice e un file people.dat contenente i dati di varie persone, potremmo leggere il file indice sequenzialmente e, per ogni indice letto, fare un posizionamento sul file dati, per leggere in quest'ultimo il record che ci interessa:
index_format = struct.Struct('<h')
with open('people.idx', 'rb') as i:
with open('people.dat', 'rb') as f:
while True:
data = i.read(index_format.size)
if not data: break
idx = index_format.unpack(data)[0]
print('Indice %d: ' % idx, end='')
f.seek(idx*Person.struct_format.size)
data = f.read(Person.struct_format.size)
name, age = Person.struct_format.unpack(data)
p = Person(name.decode('UTF-8'), age)
print(p)
Output:
Indice 3: Nome: Carla Carletti, età: 23
Indice 1: Nome: Alberto Alberti, età: 21
Indice 0: Nome: Mario Rossi, età: 20
Indice 2: Nome: Barbara Balducci, età: 22
Se si intende modificare un record, sarà necessario fare due posizionamenti, uno per la lettura e uno, successivamente, per la scrittura. Il file dovrà essere aperto in modalità 'r+':
with open('people.dat', 'r+b') as f:
idx=2 # vogliamo modificare il record in posizione 2
f.seek(idx*Person.struct_format.size)
data = f.read(Person.struct_format.size)
name, age = Person.struct_format.unpack(data)
p = Person(name.decode('UTF-8'), age)
print(p)
p.name = 'Giorgio Giacomelli'
f.seek(idx*Person.struct_format.size)
f.write(p.getPacked())
Considerazioni sulla qualità del codice
Negli esempi sopra riportati il codice è stato ridotto all'osso per semplicità. In realtà bisognerà sempre effettuare dei controlli sulla possibilità o meno di leggere/scrivere da/su un file, e inserire il codice più problematico in blocchi try... except... finally
.