Domanda

Sto interrogando un database e archiviando i risultati usando Python e sto cercando di comprimere i dati mentre li scrivo sui file di registro. Ho qualche problema con esso, però.

Il mio codice sembra così:

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. In esecuzione bunzip2 Nel file si traduce in un file con una dimensione di 943.634 ed in esecuzione bzip2 Su ciò si traduce in 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 alla corsa bzip2 Nella riga di comando?

Ho provato il codec gzip di Python (cambiando la linea in codecs.open(archive_file, 'a+', 'zip')) per vedere se ha risolto il problema. Ricevo ancora file di grandi dimensioni, ma ottengo anche un gzip: archive_file: not in gzip format Errore quando provo a disprezzare il file. Cosa sta succedendo là?


MODIFICARE: Inizialmente avevo aperto il file in modalità Append, non in modalità di scrittura. Anche se questo può o meno essere un problema, la domanda vale ancora se il file è aperto in modalità 'W'.

È stato utile?

Soluzione

Come hanno notato altri poster, il problema è che il codecs La libreria non utilizza un encoder incrementale per codificare i dati; invece codifica ogni frammento di dati alimentati al write Metodo come blocco compresso. Questo è orribilmente inefficiente e solo una terribile decisione di progettazione per una biblioteca progettata per funzionare con i flussi.

La cosa ironica è che esiste un encoder BZ2 incrementale perfettamente ragionevole già integrato in Python. Non è difficile creare una classe "simile a un file" che fa automaticamente la cosa corretta.

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ù flussi compressi a un singolo file funziona perfettamente con bunzip2, ma Python stesso non può gestirlo (anche se lì è una patch per questo). Se è necessario leggere i file compressi che creano di nuovo in Python, attenersi a un singolo flusso per file.

Altri suggerimenti

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

Proverei a costruire una stringa molto più grande (o elenco di stringhe se sei preoccupato per le prestazioni) in memoria prima di scriverlo al file. Una buona dimensione per cui sparare sarebbe 900k (o più) in quanto questa è la dimensione del blocco utilizzata da BZIP2

Il problema è dovuto all'uso della modalità Append, che si traduce in file che contengono più blocchi di dati compressi. 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 dimensioni. Vediamo cosa contiene:

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

Ok, 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 e il suo contenuto è:

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

Quello che sta succedendo qui è che Unzip si aspetta un singolo flusso zippato. 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 è quello 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 tramite Bunzip2, stai tornando solo il primo set di dischi che hai scritto; Il resto è scartato.

Non sono sicuro di quanto sia diverso dal modo di farlo codecs, ma se usi gzipfile dal modulo GZIP è possibile aggiungere in modo incrementale al file ma non si manterrà molto bene a meno che tu non stia scrivendo grandi quantità di dati su a tempo (forse> 1 kb). Questa è solo la natura degli algoritmi di compressione. Se i dati che stai scrivendo non sono molto importanti (ad esempio, puoi gestire perderli se il tuo processo muore), potresti scrivere una classe gzipfile bufferita che avvolge la classe importata che scrive più grandi blocchi di dati.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top