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.

results matching ""

    No results matching ""