2010-09-29 11 views
8

Sto interrogando un database e archiviando i risultati usando Python, e sto provando a comprimere i dati mentre li scrivo nei file di registro. Ho qualche problema con questo, però.Come funzionano i codec di compressione in Python?

Il mio codice simile a questo:

log_file = codecs.open(archive_file, 'w', 'bz2') 
for id, f1, f2, f3 in cursor: 
    log_file.write('%s %s %s %s\n' % (id, f1 or 'NULL', f2 or 'NULL', f3)) 

Tuttavia, il mio file di output ha una dimensione di 1.409.780. L'esecuzione di bunzip2 sul file ha come risultato un file con una dimensione di 943.634 e l'esecuzione di bzip2 su tale risultato ha una dimensione di 217.275. In altre parole, il file non compresso è significativamente più piccolo del file compresso usando il codec bzip di Python. C'è un modo per risolvere questo problema, oltre a eseguire bzip2 nella riga di comando?

Ho provato il codec gzip di Python (modificando la linea su codecs.open(archive_file, 'a+', 'zip')) per vedere se ha risolto il problema. Ottengo ancora file di grandi dimensioni, ma ho anche un errore gzip: archive_file: not in gzip format quando provo a decomprimere il file. Cosa sta succedendo lì?


EDIT: Originariamente ho avuto il file aperto in modalità di aggiunta, non in modalità scrivere. Anche se questo può o non può essere un problema, la domanda rimane valida se il file è aperto in modalità 'w'.

+0

Perché stai aprendo il file per l'aggiunta? – JoshD

+0

Questo taglia gradualmente i record dal database e li salva in un file di archivio, quindi il file di archivio cresce gradualmente finché non viene copiato dalla macchina stessa. –

risposta

2

Come altri utenti hanno notato, il problema è che la libreria codecs non utilizza un encoder incrementale per codificare i dati ; invece codifica ogni snippet di dati inviati al metodo write come blocco compresso. Questo è orribilmente inefficiente, e solo una terribile decisione progettuale per una biblioteca progettata per funzionare con flussi.

La cosa ironica è che esiste un encoder bz2 incrementale perfettamente ragionevole già incorporato in Python. Non è difficile creare una classe "file-like" che faccia la cosa giusta automaticamente.

import bz2 

class BZ2StreamEncoder(object): 
    def __init__(self, filename, mode): 
     self.log_file = open(filename, mode) 
     self.encoder = bz2.BZ2Compressor() 

    def write(self, data): 
     self.log_file.write(self.encoder.compress(data)) 

    def flush(self): 
     self.log_file.write(self.encoder.flush()) 
     self.log_file.flush() 

    def close(self): 
     self.flush() 
     self.log_file.close() 

log_file = BZ2StreamEncoder(archive_file, 'ab') 

Un avvertimento: In questo esempio, ho aperto il file in modalità append; l'aggiunta di più stream compressi a un singolo file funziona perfettamente con bunzip2, ma Python stesso non può gestirlo (sebbene ci sia is a patch per esso). Se è necessario leggere i file compressi che si creano nuovamente in Python, attenersi a un singolo flusso per file.

0

Il problema è dovuto all'utilizzo della modalità append, che genera file contenenti più blocchi compressi di dati. Guarda questo esempio:

>>> import codecs 
>>> with codecs.open("myfile.zip", "a+", "zip") as f: 
>>>  f.write("ABCD") 

Sul mio sistema, questo produce un file di 12 byte di dimensione. Vediamo cosa contiene:

>>> with codecs.open("myfile.zip", "r", "zip") as f: 
>>>  f.read() 
'ABCD' 

Okay, ora facciamo un'altra scrittura in modalità append:

>>> with codecs.open("myfile.zip", "a+", "zip") as f: 
>>>  f.write("EFGH") 

Il file è ora di 24 byte di dimensione, e il suo contenuto sono:

>>> with codecs.open("myfile.zip", "r", "zip") as f: 
>>>  f.read() 
'ABCD' 

Quello che sta succedendo qui è che unzip si aspetta un singolo flusso compresso. Dovrai controllare le specifiche per vedere quale sia il comportamento ufficiale con più flussi concatenati, ma nella mia esperienza elaborano il primo e ignorano il resto dei dati. Questo è ciò che fa Python.

Mi aspetto che bunzip2 stia facendo la stessa cosa. Quindi in realtà il tuo file è compresso, ed è molto più piccolo dei dati che contiene. Ma quando lo esegui attraverso bunzip2, stai recuperando solo il primo set di record che hai scritto; il resto viene scartato.

+0

Innanzitutto, i differenziali di dimensione sono il risultato dell'esecuzione del programma una volta. L'esecuzione con "w" produce esattamente lo stesso file di "a +", che è circa il 30% più grande della versione non compressa. Secondo, anche se Python non legge oltre il primo blocco di dati compresso, fa "bunzip2". –

0

Non sono sicuro di quanto sia diverso dal modo in cui i codec lo fanno ma se si utilizza GzipFile dal modulo gzip è possibile aggiungere in modo incrementale al file ma non si comprimerà molto bene a meno che non si stiano scrivendo grandi quantità di dati alla volta (forse> 1 KB). Questa è solo la natura degli algoritmi di compressione. Se i dati che stai scrivendo non sono molto importanti (ad esempio puoi occuparti di perderlo se il tuo processo muore), allora potresti scrivere una classe GzipFile bufferizzata che avvolge la classe importata che scrive blocchi di dati più grandi.

1

Il problema sembra essere che l'output viene scritto su ogni write(). Questo fa sì che ogni linea venga compressa nel proprio blocco bzip.

Vorrei provare a creare una stringa molto più grande (o un elenco di stringhe se siete preoccupati per le prestazioni) in memoria prima di scriverlo sul file. Una buona dimensione per scattare sarebbe di 900 K (o più) in quanto è la dimensione del blocco che bzip2 utilizza