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.
$ 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.