zlib - Accesso a basso livello alla libreria di compressione GNU zlib

Scopo Accesso a basso livello alla libreria di compressione GNU zlib
Versione Python 2.5 e successive

Il modulo zlib fornisce una interfaccia a basso livello a molte delle funzioni nella libreria di compressione zlib di GNU.

Lavorare con Dati in Memoria

Il modo più semplice per lavorare con zlib richiede che tutti i dati da comprimere o decomprimere siano mantenuti in memoria, quindi si usa compress() oppure decompress().

import zlib
import binascii

original_data = 'Questo è il testo originale.'
print 'Originale    :', len(original_data), original_data

compressed = zlib.compress(original_data)
print 'Compresso    :', len(compressed), binascii.hexlify(compressed)

decompressed = zlib.decompress(compressed)
print 'Decompresso  :', len(decompressed), decompressed
$ python zlib_memory.py
Originale    : 29 Questo è il testo originale.
Compresso    : 34 789c0b2c4d2d2ec95738bc42213347a104ccce2fca4ccfcc4bcc49d50300ae4e0b59
Decompresso  : 29 Questo è il testo originale.

Si noti che, per testi brevi, la versione compressa può essere di dimensioni maggiori. Sebbene i risultati effettivi dipendano dai dati in input, per piccole porzioni di testo è interessante osservare il sovraccarico di compressione.

import zlib

original_data = 'Questo è il testo originale.'

fmt = '%15s  %15s'
print fmt % ('len(dati)', 'len(compressi)')
print fmt % ('-' * 15, '-' * 15)

for i in xrange(20):
    data = original_data * i
    compressed = zlib.compress(data)
    print fmt % (len(data), len(compressed)), '*' if len(data) < len(compressed) else ''
$ python zlib_lengths.py
      len(dati)   len(compressi)
---------------  ---------------
              0                8 *
             29               34 *
             58               37
             87               37
            116               37
            145               37
            174               37
            203               37
            232               37
            261               37
            290               39
            319               39
            348               39
            377               39
            406               39
            435               39
            464               39
            493               39
            522               39
            551               41

Lavorare con i Flussi

L'approccio in-memoria presenta ovvii inconvenienti che lo rendono impraticabile per casi da usare nel mondo reale. L'alternativa è l'uso di oggetti Compress e Decompress per manipolare i flussi di dati, in modo che non occorra introdurre in memoria l'intero insieme di dati.

Il semplice server qui sotto risponde a delle richieste relative a nomi di file, scrivendone una versione compressa al socket usato per comunicare con il client. Ha una spezzettatura artificale sul posto per illustrare il comportamento di buffering che si verifica quando i dati passati a compress() o decompress() non risultano in un blocco completo di dati compressi o decompressi in uscita.

Il server ha delle ovvie carenze di sicurezza. Non eseguirlo su un server in una internet aperta od in qualsivoglia ambiente dove la sicurezza potrebbe essere un problema.
import zlib
import logging
import SocketServer
import binascii

BLOCK_SIZE = 64

class ZlibRequestHandler(SocketServer.BaseRequestHandler):

    logger = logging.getLogger('Server')

    def handle(self):
        compressor = zlib.compressobj(1)

        # Scopre cosa vuole il client
        filename = self.request.recv(1024)
        self.logger.debug('il client richiede: "%s"', filename)

        # Invia pezzi del file mentre vengono compressi
        with open(filename, 'rb') as input:
            while True:
                block = input.read(BLOCK_SIZE)
                if not block:
                    break
                self.logger.debug('RAW "%s"', block)
                compressed = compressor.compress(block)
                if compressed:
                    self.logger.debug('IN INVIO "%s"', binascii.hexlify(compressed))
                    self.request.send(compressed)
                else:
                    self.logger.debug('BUFFERING')

        # Invia tutti i dati che il compressore ha nel buffer
        remaining = compressor.flush()
        while remaining:
            to_send = remaining[:BLOCK_SIZE]
            remaining = remaining[BLOCK_SIZE:]
            self.logger.debug('SVUOTAMENTO "%s"', binascii.hexlify(to_send))
            self.request.send(to_send)
        return


if __name__ == '__main__':
    import socket
    import threading
    from cStringIO import StringIO

    logging.basicConfig(level=logging.DEBUG,
                        format='%(name)s: %(message)s',
                        )
    logger = logging.getLogger('Client')

    # Imposta un server, in esecuzione su di un thread separato
    address = ('localhost', 0) # lasciamo che il kernel ci dia una porta
    server = SocketServer.TCPServer(address, ZlibRequestHandler)
    ip, port = server.server_address # scopriamo che porta abbiamo ottenuto

    t = threading.Thread(target=server.serve_forever)
    t.setDaemon(True)
    t.start()

    # Connessione al server
    logger.info('Contatto il server su %s:%s', ip, port)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((ip, port))

    # Richiesta di un file
    requested_file = 'lorem.txt'
    logger.debug('invio nome file: "%s"', requested_file)
    len_sent = s.send(requested_file)

    # Ricezione della risposta
    buffer = StringIO()
    decompressor = zlib.decompressobj()
    while True:
        response = s.recv(BLOCK_SIZE)
        if not response:
            break
        logger.debug('LETTURA "%s"', binascii.hexlify(response))

        # Include any unconsumed data when feeding the decompressor.
        # Comprende tutti i dati non utilizzati quando si alimenta il decompressore
        to_decompress = decompressor.unconsumed_tail + response
        while to_decompress:
            decompressed = decompressor.decompress(to_decompress)
            if decompressed:
                logger.debug('DECOMPRESSIONE "%s"', decompressed)
                buffer.write(decompressed)
                # Cerca dati inutilizzati a causa del buffer overflow
                to_decompress = decompressor.unconsumed_tail
            else:
                logger.debug('BUFFERING')
                to_decompress = None

    # Si occupa dei dati rimasti all'interno del buffer del decompressore
    remainder = decompressor.flush()
    if remainder:
        logger.debug('SVUOTATI "%s"', remainder)
        buffer.write(reaminder)

    full_response = buffer.getvalue()
    lorem = open('lorem.txt', 'rt').read()
    logger.debug('la risposta corrisponde al contenuto del file: %s', full_response == lorem)

    # Pulizia
    s.close()
    server.socket.close()
$ python zlib_server.py
$ python zlib_
zlib_checksums.py        zlib_lengths.py          zlib_mixedr.py
zlib_checksums_tests.py  zlib_memory.py           zlib_server.py
robby@robby-desktop:~/pydev/pymotw-it/dumpscripts$ python zlib_server.py
Client: Contatto il server su 127.0.0.1:49846
Client: invio nome file: "lorem.txt"
Server: il client richiede: "lorem.txt"
Server: RAW "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
Vivamu"
Server: IN INVIO "7801"
Server: RAW "s eget elit. In posuere mi non risus. Mauris id quam posuere
lec"
Server: BUFFERING
Server: RAW "tus sollicitudin varius. Praesent at mi. Nunc eu velit. Sed augu"
Server: BUFFERING
Server: RAW "e
massa, fermentum id, nonummy a, nonummy sit amet, ligula. Cura"
Server: BUFFERING
Server: RAW "bitur
eros pede, egestas at, ultricies ac, pellentesque eu, tell"
Server: BUFFERING
Server: RAW "us.

Sed sed odio sed mi luctus mollis. Integer et nulla ac aug"
Server: BUFFERING
Server: RAW "ue convallis
accumsan. Ut felis. Donec lectus sapien, elementum "
Server: BUFFERING
Server: RAW "nec, condimentum ac,
interdum non, tellus. Aenean viverra, mauri"
Server: BUFFERING
Server: RAW "s vehicula semper porttitor,
ipsum odio consectetuer lorem, ac i"
Server: BUFFERING
Server: RAW "mperdiet eros odio a sapien. Nulla
mauris tellus, aliquam non, e"
Server: BUFFERING
Server: RAW "gestas a, nonummy et, erat. Vivamus
sagittis porttitor eros.
"
Server: BUFFERING
Server: SVUOTAMENTO "55524b8a1c310cddfb143a40517708c96660120221d96b6c4d45e04f8d6515e4f6797677a7c9a2c0654b7a3fbdb62e85f4342f945a6e9d4c077191b1516cd524"
Server: SVUOTAMENTO "0e192e9d38e9a916b51e2459c71e7ee9c5c58de49071bba2974a6733540b15a5da2a7535b79dbeb2e3449ae8c3b93c8a42c6744cb096b3461d9eb4d2c55d67cb"
Server: SVUOTAMENTO "f7ce6252c16560d84edfbc4612a76ba1d30f49c47eb884c266bcd1bbf48272c8d0b44d6c2fe50fe1e1717ceaca7a78e69d3e7be737c0f620bd199d92649b726c"
Server: SVUOTAMENTO "b0017623cfa38398e02f6e78cf1908621f2e60b2d1c0c5a41ac26463f85ad2b60e909f7d892b531c8a5ed079c04798553d67c6c89b8069f3c5b328708c5e8ceb"
Server: SVUOTAMENTO "4e3f0704adbe2fad4aa487537caa5490cc72178bc71554d2fb05980605564fb002da9f343f491586c17a49ef30a6dc42b9e4b746f801dee504c1b3f53174b48e"
Server: SVUOTAMENTO "396b2d96a8ff76016b22659b0a74b6249d1b302d5ca598b468cecc201401adf06f6ea12beb5a82c5ed9fdbcf9ce6e649e7b1d37dc382f1a1a084841ed416da1e"
Server: SVUOTAMENTO "fe028386fcd5"
Client: LETTURA "780155524b8a1c310cddfb143a40517708c96660120221d96b6c4d45e04f8d6515e4f6797677a7c9a2c0654b7a3fbdb62e85f4342f945a6e9d4c077191b1516c"
Client: DECOMPRESSIONE "Lorem ipsum dolor sit amet, co"
Client: LETTURA "d5240e192e9d38e9a916b51e2459c71e7ee9c5c58de49071bba2974a6733540b15a5da2a7535b79dbeb2e3449ae8c3b93c8a42c6744cb096b3461d9eb4d2c55d"
Client: DECOMPRESSIONE "nsectetuer adipiscing elit.
Vivamus eget elit. In posuere mi non risus. Mauris id quam posuere
lectus sollicitudin var"
Client: LETTURA "67cbf7ce6252c16560d84edfbc4612a76ba1d30f49c47eb884c266bcd1bbf48272c8d0b44d6c2fe50fe1e1717ceaca7a78e69d3e7be737c0f620bd199d92649b"
Client: DECOMPRESSIONE "ius. Praesent at mi. Nunc eu velit. Sed augue
massa, fermentum id, nonummy a, nonummy sit amet, ligula. Curabitur
eros pede,"
Client: LETTURA "726cb0017623cfa38398e02f6e78cf1908621f2e60b2d1c0c5a41ac26463f85ad2b60e909f7d892b531c8a5ed079c04798553d67c6c89b8069f3c5b328708c5e"
Client: DECOMPRESSIONE " egestas at, ultricies ac, pellentesque eu, tellus.

Sed sed odio sed mi luctus mollis. Integer et nulla ac augue convallis
accu"
Client: LETTURA "8ceb4e3f0704adbe2fad4aa487537caa5490cc72178bc71554d2fb05980605564fb002da9f343f491586c17a49ef30a6dc42b9e4b746f801dee504c1b3f53174"
Client: DECOMPRESSIONE "msan. Ut felis. Donec lectus sapien, elementum nec, condimentum ac,
interdum non, tellus. Aenean viverra, mauris vehicula semper portti"
Client: LETTURA "b48e396b2d96a8ff76016b22659b0a74b6249d1b302d5ca598b468cecc201401adf06f6ea12beb5a82c5ed9fdbcf9ce6e649e7b1d37dc382f1a1a084841ed416"
Client: DECOMPRESSIONE "tor,
ipsum odio consectetuer lorem, ac imperdiet eros odio a sapien. Nulla
mauris tellus, aliquam non, egestas a, nonummy et, erat. Vivamus
sagittis porttitor"
Client: LETTURA "da1efe028386fcd5"
Client: DECOMPRESSIONE " eros.
"
Client: la risposta corrisponde al contenuto del file: True

Flussi con Contenuto Misto

La classe Decompress restituita da decompressobj() può essere anche usata in situazioni dove i dati compressi e non compressi sono mescolati assieme. Dopo la decompressione di tutti i dati, l'attributo usused_data contiene tutti i dati che non sono stati usati.

import zlib

lorem = open('lorem.txt', 'rt').read()
compressed = zlib.compress(lorem)
combined = compressed + lorem

decompressor = zlib.decompressobj()
decompressed = decompressor.decompress(combined)

print 'Decompressi che corrispondono a lorem       :', decompressed == lorem
print 'Dati inutilizzati che corrispondono a lorem :', decompressor.unused_data == lorem
$ python zlib_mixed.py
Decompressi che corrispondono a lorem       : True
Dati inutilizzati che corrispondono a lorem : True

Checksum

Oltre alle funzioni di compressione e decompressione, zlib comprende due funzioni per calcolare i checksum dei dati, adler32() e crc32(). Nessuno dei due checksum può etichettarsi come crittograficamente sicuro, e sono intesi solamente per l'uso in fase di verifica dell'integrità dei dati.

Entrambe le funzioni richiedono gli stessi parametri, una stringa di dati ed un valore opzionale da usarsi come punto di partenza per il checksum. Esse restituiscono un valore intero con segno a 32-bit che può anche essere ripassato in seguito a chiamate successive, come nuovo parametro di valore di partenza per produrre un cheksum in esecuzione

import zlib

data = open('lorem.txt', 'r').read()

cksum = zlib.adler32(data)
print 'Adler32: %12d' % cksum
print '       : %12d' % zlib.adler32(data, cksum)

cksum = zlib.crc32(data)
print 'CRC-32 : %12d' % cksum
print '       : %12d' % zlib.crc32(data, cksum)
$ python zlib_checksums.py
Adler32:  -2088305451
       :  -2144601672
CRC-32 :   -759833719
       :   1579823464

L'algoritmo Adler32 è ritenuto più veloce rispetto ad un CRC standard, ma ho scoperto che, in alcuni test, è più lento.

import timeit

iterations = 1000

def show_results(title, result, iterations):
    "Stampa i risultati in microsecondi per passaggio e per voce"
    per_pass = 1000000 * (result / iterations)
    print '%s:\t%.2f usec/passaggio' % (title, per_pass)


adler32 = timeit.Timer(
    stmt="zlib.adler32(data)",
    setup="import zlib; data=open('lorem.txt','r').read() * 10",
    )
show_results('Adler32, separato     ', adler32.timeit(iterations), iterations)

adler32_running = timeit.Timer(
    stmt="cksum = zlib.adler32(data, cksum)",
    setup="import zlib; data=open('lorem.txt','r').read() * 10; cksum = zlib.adler32(data)",
    )
show_results('Adler32, in esecuzione', adler32_running.timeit(iterations), iterations)

crc32 = timeit.Timer(
    stmt="zlib.crc32(data)",
    setup="import zlib; data=open('lorem.txt','r').read() * 10",
    )
show_results('CRC-32, separato     ', crc32.timeit(iterations), iterations)

crc32_running = timeit.Timer(
    stmt="cksum = zlib.crc32(data, cksum)",
    setup="import zlib; data=open('lorem.txt','r').read() * 10; cksum = zlib.crc32(data)",
    )
show_results('CRC-32, in esecuzione', crc32_running.timeit(iterations), iterations)
$ python zlib_checksums_tests.py
Adler32, separato     :	9.93 usec/passaggio
Adler32, in esecuzione:	9.81 usec/passaggio
CRC-32, separato     :	9.22 usec/passaggio
CRC-32, in esecuzione:	9.22 usec/passaggio

Vedere anche:

zlib
La documentazione della libreria standard per questo modulo.
gzip
Il modulo gzip comprende una interfaccia a più alto livello (basata su file) alla libreria zlib.
http://www.zlib.net/
La home page della libreria zlib.
bz2
Il modulo bz2 fornisce una interfaccia similare alla libreria di compressione bzip2.