| Scopo | Accesso a basso livello alla libreria di compressione GNU zlib |
| Versione Python | 2.5 e successive |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per il modulo zlib
Il modulo zlib fornisce una interfaccia a basso livello a molte delle funzioni nella libreria di compressione zlib di GNU.
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
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.
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
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
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: