zlib - Compressione zlib GNU

Scopo: Accesso a basso livello alla libreria di compressione GNU zlib

Il modulo zlib fornisce una interfaccia a più basso livello a molte delle funzioni nella libreria di compressione zlib del progetto 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.

# zlib_memory.py

import zlib
import binascii

original_data = b"Questo e' 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)

Entrambe le funzioni compress() e decompress() ricevono una sequenza di byte come argomento e ritornano una sequenza di byte.

$ python3 zlib_memory.py

Originale    : 29 b"Questo e' il testo originale."
Compresso    : 33 b'789c0b2c4d2d2ec957485557c8cc512801b3f38b32d333f3127352f5009ba50a7a'
Decompresso  : 29 b"Questo e' il testo originale."

L'esempio precedente dimostra che la versione compressa di piccole quantità di dati può essere di dimensioni maggiori della versione non compressa. Sebbene i risultati effettivi dipendano dai dati in input, per piccole porzioni di testo è interessante osservare il sovraccarico di compressione.

# zlib_lengths.py

import zlib

original_data = b"Questo e' il testo originale."

template = '{:>15}  {:>15}'
print(template.format('len(data)', 'len(compressed)'))
print(template.format('-' * 16, '-' * 16))

for i in range(5):
    data = original_data * i
    compressed = zlib.compress(data)
    highlight = '*' if len(data) < len(compressed) else ''
    print(template.format(len(data), len(compressed)), highlight)

L'asterisco nel risultato evidenzia le righe dove i dati compressi occupano più memoria rispetto alla versione non compressa.

$ python3 zlib_lengths.py

      len(data)  len(compressed)
----------------  ----------------
              0                8 *
             29               33 *
             58               37 
             87               37 
            116               37 

zlib supporta diversi livelli di compressione, consentendo un bilanciamento tra il costo computazionale e l'ammontare della riduzione di spazio. Il livello di compressione predefinito, zlib.Z_DEFAULT_COMPRESSION è -1 e corrisponde a un valore inserito nel codice che rappresenta un compromesso tra prestazioni e compressione. Attualmente corrisponde al livello 6.

# zlib_compresslevel.py

import zlib

input_data = b'Del testo ripetuto.\n' * 1024
template = '{:>7}  {:>10}'

print(template.format('Livello', 'Dimensione'))
print(template.format('-------', '----------'))

for i in range(0, 10):
    data = zlib.compress(input_data, i)
    print(template.format(i, len(data)))

Il livello 0 equivale a nessuna compressione. Il livello 9 richiede il maggior sforzo di calcolo e produce il risultato più ridotto. Come mostra l'esempio, la stessa dimensione di riduzione può essere ottenuta da più livelli di compressione dato lo stesso input.

$ python3 zlib_compresslevel.py

Livello  Dimensione
-------  ----------
      0       20491
      1         171
      2         171
      3         171
      4          97
      5          97
      6          97
      7          97
      8          97
      9          97

Compressione e Decompressione Incrementale

L'approccio in-memoria ha degli inconvenienti che lo rendono impraticabile per casi di uso reali, in primis perchè il sistema necessita di sufficiente memoria per mantenere le versioni compresse e non compresse residenti in memoria allo stesso tempo. L'alternativa è di usare gli oggetti Compress e Decompress per manipolare i dati in modo incrementale, così che l'intero insieme di dati non debba essere contenuto in memoria.

# zlib_incremental.py

import zlib
import binascii

compressor = zlib.compressobj(1)

with open('lorem.txt', 'rb') as input:
    while True:
        block = input.read(64)
        if not block:
            break
        compressed = compressor.compress(block)
        if compressed:
            print('Compressi: {}'.format(
                binascii.hexlify(compressed)))
        else:
            print('nel buffer...')
    remaining = compressor.flush()
    print('Svuotamento: {}'.format(binascii.hexlify(remaining)))

Questo esempio legge piccoli blocchi di dati da un file a testo semplice e li passa a compress(). Il compressore mantiene un buffer interno di dati compressi. Visto che l'algoritmo di compressione dipende da cifre di controllo (checksum) e dimensioni minime di blocco, il compressore potrebbe non essere pronto a ritornare i dati ogni volta che riceve ulteriore input. Se non ha un intero blocco compresso pronto, ritorna una stringa vuota. Quando tutti i dati sono stati incamerati, il metodo flush() forza il compressore a chiudere il blocco finale e ritornare i dati compressi rimanenti.

$ python3 zlib_incremental.py

Compressi: b'7801'
nel buffer...
nel buffer...
nel buffer...
nel buffer...
nel buffer...
nel buffer...
nel buffer...
nel buffer...
nel buffer...
nel buffer...
nel buffer...
Svuotamento: b'55525b8edb300cfcf7297800c37728b09f6d51a068ff1999c9b2a024af48fafc3bea669b06300c8b1687f3e0d73ea4921e9e95f66e7d906b105789954a6f2e25245206f1ae877ad17623318d6d79e94d0ac94d3cd85792a695249e9bd28c6be9e390b192012b9d4c6f694c236360a6495f1706a4546981c204a7e8030f49d25b72dde825d529b415ddb3053575a504cd16b2d1f73965b97251437da39fb2530cf5d0b71492d17d02995ef0b9d10f31c324f1f9f3145b7894dce8b79e5cc1eec881771f4557522e0948e2b29227b41fa0f6b0e7483bb5f1a4b92e86bb0ef4c1e280a7030519fc4f8a831468dba4db0a5d8cdb0eb45d19923f3c5cf604fb277eaf7cd1804aaa7d5cf43f5598f1e1261c6f08081263a96ce2c93bd315014ede143990dae7848dbe184cc1c8534f1903170702d5e91f82b85b4957c99b823ae76d1a68a25769a03f7d7e38553961f2e30c8560306ba40d5a2faf0f13ee0a914dfa012c75173a7ac02948fef6b70bcdeec0ff15936ecc6ce62666999b705f884fdbbc9b69d1c85ddb13e8a215bbb62bfaffa447df96776b9b0ea5'

Flussi di Contenuto Combinati

La classe Decompress ritornata da decompressobj() potrebbe anche essere usata in situazioni nelle quali dati compressi e non compressi sono combinati assieme.

# zlib_mixed.py

import zlib

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

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

decompressed_matches = decompressed == lorem
print('I dati decompressi corrispondono al lorem:', decompressed_matches)

unused_matches = decompressor.unused_data == lorem
print('Dati non utilizzati corrispondono al lorem :', unused_matches)

Dopo la decompressione di tutti i dati, l'attributo unused_data contiene tutti i dati non utilizzati.

$ python3 zlib_mixed.py

I dati decompressi corrispondono al lorem: True
Dati non utilizzati corrispondono al lorem : True

Cifre di Controllo

Oltre alle funzioni di compressione e decompressione, zlib include anche due funzioni per calcolare cifre di controllo di dati, adler32() e crc32. Nessuna cifra di controllo è sicura dal punto di vista crittografico, e sono da utilizzare solo per verifiche di integrità di dati.

# zlib_checksums.py

import zlib

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

cksum = zlib.adler32(data)
print('Adler32: {:12d}'.format(cksum))
print('       : {:12d}'.format(zlib.adler32(data, cksum)))

cksum = zlib.crc32(data)
print('CRC-32 : {:12d}'.format(cksum))
print('       : {:12d}'.format(zlib.crc32(data, cksum)))

Entrambe le funzioni richiedono gli stessi argomenti, una stringa di byte che contiene i dati e un valore opzionale da usare come punto di partenza per la cifra di controllo. Esse ritornano un intero con segno a 32 bit che può anche essere ripassato su chiamate successive come nuovo punto di partenza per produrre una cifra di controllo corrente.

$ python3 zlib_checksums.py

Adler32:   1805323941
       :   4291829065
CRC-32 :   2930643990
       :    463594156

Comprimere Dati su Reti

Il server qui sotto utilizza un compressore di flusso per rispondere a richieste relative a nomi di file, scrivendone una versione compressa al socket usato per comunicare con il client.

# zlib_server.py

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 quale file vuole il client
        filename = self.request.recv(1024).decode('utf-8')
        self.logger.debug('il client richiede: %r', 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('GREZZI %r', block)
                compressed = compressor.compress(block)
                if compressed:
                    self.logger.debug(
                        'IN INVIO %r',
                        binascii.hexlify(compressed))
                    self.request.send(compressed)
                else:
                    self.logger.debug('IN BUFFER')

        # 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 %r',
                              binascii.hexlify(to_send))
            self.request.send(to_send)
        return


if __name__ == '__main__':
    import socket
    import threading
    from io import BytesIO

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

    # mposta 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  # quale porta è stata assegnata?

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

    # onnessione al server come client
    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 file con nome: %r', requested_file)
    len_sent = s.send(requested_file.encode('utf-8'))

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

        # 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('DECOMPRESSSI %r', decompressed)
                buffer.write(decompressed)
                # Cerca dati non consumati a causa del buffer overflow
                to_decompress = decompressor.unconsumed_tail
            else:
                logger.debug('IN BUFFER')
                to_decompress = None

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

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

    # Pulizia
    s.close()
    server.socket.close()

Ha una spezzettatura artificiale 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 client si connette al socket e richiede un file. Poi esegue un ciclo, ricevendo blocchi di dati compressi. Visto che un blocco potrebbe non contenere sufficienti informazioni per decomprimerlo interamente, il resto dei dati ricevuti in precedenza viene combinato con i nuovi dati e passato al decompressore. Mano a mano che i dati sono decompressi, sono aggiunti a un buffer, il contenuto del quale viene confrontato con il contenuto del file alla fine del ciclo di elaborazione.

Il server ha delle ovvie carenze di sicurezza. Non eseguirlo su un server in una internet aperta o in qualsivoglia ambiente dove la sicurezza potrebbe essere un problema.
$ python3 zlib_server.py

Client: Contatto il server su 127.0.0.1:37939
Client: invio file con nome: 'lorem.txt'
Server: il client richiede: 'lorem.txt'
Server: GREZZI b'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.\nDonec '
Server: IN INVIO b'7801'
Server: GREZZI b'egestas, enim et consectetuer ullamcorper, lectus ligula rutrum '
Server: IN BUFFER
Server: GREZZI b'leo,\na elementum elit tortor eu quam. Duis tincidunt nisi ut ant'
Server: IN BUFFER
Server: GREZZI b'e. Nulla\nfacilisi. Sed tristique eros eu libero. Pellentesque ve'
Client: READ b'7801'
Server: IN BUFFER
Client: IN BUFFER
Server: GREZZI b'l\narcu. Vivamus purus orci, iaculis ac, suscipit sit amet, pulvi'
Server: IN BUFFER
Server: GREZZI b'nar eu,\nlacus. Praesent placerat tortor sed nisl. Nunc blandit d'
Server: IN BUFFER
Server: GREZZI b'iam egestas\ndui. Pellentesque habitant morbi tristique senectus '
Server: IN BUFFER
Server: GREZZI b'et netus et\nmalesuada fames ac turpis egestas. Aliquam viverra f'
Server: IN BUFFER
Server: GREZZI b'ringilla\nleo. Nulla feugiat augue eleifend nulla. Vivamus mauris'
Server: IN BUFFER
Server: GREZZI b'. Vivamus sed\nmauris in nibh placerat egestas. Suspendisse poten'
Server: IN BUFFER
Server: GREZZI b'ti. Mauris\nmassa. Ut eget velit auctor tortor blandit sollicitud'
Server: IN BUFFER
Server: GREZZI b'in. Suspendisse\nimperdiet justo.\n'
Server: IN BUFFER
Server: SVUOTAMENTO b'55525b8edb300cfcf7297800c37728b09f6d51a068ff1999c9b2a024af48fafc3bea669b06300c8b1687f3e0d73ea4921e9e95f66e7d906b105789954a6f2e25'
Client: READ b'55525b8edb300cfcf7297800c37728b09f6d51a068ff1999c9b2a024af48fafc3bea669b06300c8b1687f3e0d73ea4921e9e95f66e7d906b105789954a6f2e25'
Server: SVUOTAMENTO b'245206f1ae877ad17623318d6d79e94d0ac94d3cd85792a695249e9bd28c6be9e390b192012b9d4c6f694c236360a6495f1706a4546981c204a7e8030f49d25b'
Client: DECOMPRESSSI b'Lorem ipsum dolor sit amet, consec'
Server: SVUOTAMENTO b'72dde825d529b415ddb3053575a504cd16b2d1f73965b97251437da39fb2530cf5d0b71492d17d02995ef0b9d10f31c324f1f9f3145b7894dce8b79e5cc1eec8'
Client: READ b'245206f1ae877ad17623318d6d79e94d0ac94d3cd85792a695249e9bd28c6be9e390b192012b9d4c6f694c236360a6495f1706a4546981c204a7e8030f49d25b'
Server: SVUOTAMENTO b'81771f4557522e0948e2b29227b41fa0f6b0e7483bb5f1a4b92e86bb0ef4c1e280a7030519fc4f8a831468dba4db0a5d8cdb0eb45d19923f3c5cf604fb277eaf'
Client: DECOMPRESSSI b'tetuer adipiscing elit.\nDonec egestas, enim et consectetuer ullamcorper, lectus ligula rutrum leo,\na elementum elit tortor eu q'
Server: SVUOTAMENTO b'7cd1804aaa7d5cf43f5598f1e1261c6f08081263a96ce2c93bd315014ede143990dae7848dbe184cc1c8534f1903170702d5e91f82b85b4957c99b823ae76d1a'
Client: READ b'72dde825d529b415ddb3053575a504cd16b2d1f73965b97251437da39fb2530cf5d0b71492d17d02995ef0b9d10f31c324f1f9f3145b7894dce8b79e5cc1eec8'
Server: SVUOTAMENTO b'68a25769a03f7d7e38553961f2e30c8560306ba40d5a2faf0f13ee0a914dfa012c75173a7ac02948fef6b70bcdeec0ff15936ecc6ce62666999b705f884fdbbc'
Client: DECOMPRESSSI b'uam. Duis tincidunt nisi ut ante. Nulla\nfacilisi. Sed tristique eros eu libero. Pellentesque vel\narcu. Vivamus pu'
Server: SVUOTAMENTO b'9b69d1c85ddb13e8a215bbb62bfaffa447df96776b9b0ea5'
Client: READ b'81771f4557522e0948e2b29227b41fa0f6b0e7483bb5f1a4b92e86bb0ef4c1e280a7030519fc4f8a831468dba4db0a5d8cdb0eb45d19923f3c5cf604fb277eaf'
Client: DECOMPRESSSI b'rus orci, iaculis ac, suscipit sit amet, pulvinar eu,\nlacus. Praesent placerat tortor sed nisl. Nunc blandit diam egestas\ndui. Pellentesque h'
Client: READ b'7cd1804aaa7d5cf43f5598f1e1261c6f08081263a96ce2c93bd315014ede143990dae7848dbe184cc1c8534f1903170702d5e91f82b85b4957c99b823ae76d1a'
Client: DECOMPRESSSI b'abitant morbi tristique senectus et netus et\nmalesuada fames ac turpis egestas. Aliquam viverra fringilla\nleo. Nulla feugiat aug'
Client: READ b'68a25769a03f7d7e38553961f2e30c8560306ba40d5a2faf0f13ee0a914dfa012c75173a7ac02948fef6b70bcdeec0ff15936ecc6ce62666999b705f884fdbbc'
Client: DECOMPRESSSI b'ue eleifend nulla. Vivamus mauris. Vivamus sed\nmauris in nibh placerat egestas. Suspendisse potenti. Mauris\nmassa. Ut eget velit auctor tortor blandit s'
Client: READ b'9b69d1c85ddb13e8a215bbb62bfaffa447df96776b9b0ea5'
Client: DECOMPRESSSI b'ollicitudin. Suspendisse\nimperdiet justo.\n'
Client: la risposta corrisponde al contenuto del file: True

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.
zlib 1.2.11 Manual
Documentazione completa di zlib
bz2 (Di prossima traduzione)
Il modulo bz2 fornisce una interfaccia similare alla libreria di compressione bzip2.