bz2 - Compressione bzip2
Scopo: Il modulo bz2 è una interfaccia per la libreria bzip2, usata per comprimere dati per conservazione o trasmissione.
Sono fornite tre API:
- - funzioni di compressione/decompressione "una tantum" per operare su di una massa di dati
- - oggetti di compressione/decompressione interattivi per lavorare con flussi di dati
- - una classe tipo file che supporta la lettura e la scrittura come un file non compresso
Operazioni in Memoria Una Tantum
Il modo più semplice per lavorare con bz2 è di caricare tutti i dati da comprimere o decomprimere in memoria, quindi usare compress()
e decompress()
per trasformarli.
# bz2_memory.py
import bz2
import binascii
original_data = b"Questo e' il testo originale."
print('Originale : {} byte'.format(len(original_data)))
print(original_data)
print()
compressed = bz2.compress(original_data)
print('Compresso : {} byte'.format(len(compressed)))
hex_version = binascii.hexlify(compressed)
for i in range(len(hex_version) // 40 + 1):
print(hex_version[i * 40:(i + 1) * 40])
print()
decompressed = bz2.decompress(compressed)
print('Decompresso : {} byte'.format(len(decompressed)))
print(decompressed)
I dati compressi contengono caratteri non ASCII, quindi devono essere convertiti nella rappresentazione esadecimale prima di essere stampati.
$ python3 bz2_memory.py Originale : 29 byte b"Questo e' il testo originale." Compresso : 63 byte b'425a6839314159265359b99a9fef000003138040' b'81200022a59e00200021a99320c9e50a600029db' b'bb135a84bc64e4b488bb0e82f8bb9229c28485cc' b'd4ff78' Decompresso : 29 byte b"Questo e' il testo originale."
Per testi brevi, la dimensione della versione compressa può essere significativamente più grande di quella originale. Mentre il risultato reale dipende dai dati in input, è interessante osservare il ricarico di compressione.
# bz2_lengths.py
import bz2
original_data = b"Questo e' il testo originale."
fmt = '{:>15} {:>15}'
print(fmt.format('len(dati)', 'len(compressed)'))
print(fmt.format('-' * 15, '-' * 15))
for i in range(5):
data = original_data * i
compressed = bz2.compress(data)
print(fmt.format(len(data), len(compressed)), end='')
print('*' if len(data) < len(compressed) else '')
Le righe che nel risultato finiscono con * mostrano i punti dove la dimensione dei dati compressi è più grande degli originali.
$ python3 bz2_lengths.py len(dati) len(compressed) --------------- --------------- 0 14* 29 63* 58 70* 87 71 116 73
Compressione e Decompressione Incrementale
L'approccio "in memoria" ha ovvi inconvenienti che lo rendono improponibile per usi pratici. L'alternativa è di usare gli oggetti BZ2Compressor
e BZ2Decompressor
per manipolare i dati in modo incrementale così che l'intero insieme di dati non debba essere accomodato in memoria.
# bz2_incremental.py
import bz2
import binascii
import io
compressor = bz2.BZ2Compressor()
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('accumulo...')
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 e dimensioni minime di blocco, il compressore potrebbe non essere pronto per ritornare dati ogni volta che ne riceve ulteriori in input. Se non ha un intero blocco compresso pronto, ritorna una stringa vuota. Quando tutti i dati sono stati passati, il metodo flush()
forza il compressore a chiudere il blocco finale e ritornare i dati compressi rimasti.
$ python3 bz2_incremental.py accumulo... accumulo... accumulo... accumulo... accumulo... accumulo... accumulo... accumulo... accumulo... accumulo... accumulo... accumulo... Svuotamento: b'425a6839314159265359ef75176500004357800010400524074b003ff7ff004001d1c6820d0988a7e5349e88d000d06a6342298d4d3401a640634c4611a6000004a6441354f4c9a483d47a87a8306efbf1e657e1e92000a3e1a0eab2c3505d85acfe24c14151142e5e597e9046c9886d5ab53365e0e9020b30afbc8acbe3be0d6896aa43cc54e6caeb75c860ba68888294a85badd1d3380a36c16c66ee43dee7fa24ef3c7690db6d173cd14573633d4854be4bdcc60e119c0cca77ab4d82be33b5e123e2f22dd9eb304f0c48f46f5dffa66ea8d7766e5fea2f79df1d6c790d5ce94dcb1d5471e1e115f3072d55d8f1887a374be707659a519b2ad7135d551b83064d52e690a760a2e2a6c99c6bdc6fbf3b62375a387a3182475f2e57acf8c436a19f8198554e4cc61d287670a2caa24bbb509a24ab53414bc2ebb75faf05e385f6dbf76a8dbc68785c9984849525bf270a197f2c929bf3d46e5152dc4ceafaba28bb702a1a7a52cfb988c58625624bd0c112fb59d14ad2dfb9c245454c33215b23916d45992d08456c2aa8c0c8ebb88bf8a0081d76d9edf84ce994f178925732e6e622de86f2fa1203ec9fc5dc914e14243bdd45d940'
Flussi di Contenuto Misto
BZ2Decompressor
può anche essere usato in situazioni dove dati compressi e non compressi sono mescolati.
# bz2_mixed.py
import bz2
lorem = open('lorem.txt', 'rt').read().encode('utf-8')
compressed = bz2.compress(lorem)
combined = compressed + lorem
decompressor = bz2.BZ2Decompressor()
decompressed = decompressor.decompress(combined)
decompressed_matches = decompressed == lorem
print('Decompressi che corrispondono a lorem :', decompressed_matches)
unused_matches = decompressor.unused_data == lorem
print('Dati inutilizzati che corrispondono a lorem :', unused_matches)
Dopo la decompressione di tutti i dati, l'attributo unused_data
contiene qualunque dato che non sia stato usato.
$ python3 bz2_mixed.py Decompressi che corrispondono a lorem : True Dati inutilizzati che corrispondono a lorem : True
Scrivere Dati Compressi
BZ2File
può anche essere usato per scrivere verso e leggere da file compressi con bzip2 usando gli abituali metodi per leggere e scrivere dati..
# bz2_file_write.py
import bz2
import io
import os
data = 'Il contenuto del file esempio va qui.\n'
with bz2.BZ2File('esempio.bz2', 'wb') as output:
with io.TextIOWrapper(output, encoding='utf-8') as enc:
enc.write(data)
os.system('file esempio.bz2')
Per scrivere dati in un file compresso, lo si apra con modalità 'wb'
. Questo esempio impacchetta il file aperto con BZ2File
con un TextIOWrapper
dal modulo io per codificare testo Unicode in byte adatti alla compressione.
$ python3 bz2_file_write.py esempio.bz2: bzip2 compressed data, block size = 900k
E' possibile usare diversi livelli di compressione tramite l'argomento compresslevel
. L'intervallo di valori valido è da 1 a 9 compreso. Valori bassi generano una compressione più veloce ma di maggiori dimensioni. Valori alti producono una compressione più lenta e più compatta.
# bz2_file_compresslevel.py
import bz2
import io
import os
data = open('lorem.txt', 'r', encoding='utf-8').read() * 1024
print('Input coniene {} byte'.format(
len(data.encode('utf-8'))))
for i in range(1, 10):
filename = 'livello-di-compressione-{}.bz2'.format(i)
with bz2.BZ2File(filename, 'wb', compresslevel=i) as output:
with io.TextIOWrapper(output, encoding='utf-8') as enc:
enc.write(data)
os.system('cksum {}'.format(filename))
La colonna centrale di cifre nel risultato dello script rappresenta la dimensione in byte dei file prodotti. Per questi dati in input, valori di compression più alti non sempre si traducono in uno spazio di conservazione minore per gli stessi dati in input. I risultati possono variare per altri input.
$ python3 bz2_file_compresslevel.py Input coniene 754688 byte 4105199836 8722 livello-di-compressione-1.bz2 2875524866 4918 livello-di-compressione-2.bz2 2466635111 3681 livello-di-compressione-3.bz2 2441059017 2697 livello-di-compressione-4.bz2 348190677 2687 livello-di-compressione-5.bz2 1474295117 2556 livello-di-compressione-6.bz2 4077188232 2382 livello-di-compressione-7.bz2 3725855634 1129 livello-di-compressione-8.bz2 3022461369 1129 livello-di-compressione-9.bz2
Una istanza di BZ2File
include anche un metodo writelines()
che può essere usato per scrivere una sequenza di stringhe.
# bz2_file_writelines.py
import bz2
import io
import itertools
import os
data = 'La stessa riga, ripetutamente.\n'
with bz2.BZ2File('righe.bz2', 'wb') as output:
with io.TextIOWrapper(output, encoding='utf-8') as enc:
enc.writelines(itertools.repeat(data, 10))
os.system('bzcat righe.bz2')
Le righe dovrebbero finire con un carattere di ritorno a capo, come per la scrittura di un file normale.
$ python3 bz2_file_writelines.py La stessa riga, ripetutamente. La stessa riga, ripetutamente. La stessa riga, ripetutamente. La stessa riga, ripetutamente. La stessa riga, ripetutamente. La stessa riga, ripetutamente. La stessa riga, ripetutamente. La stessa riga, ripetutamente. La stessa riga, ripetutamente. La stessa riga, ripetutamente.
Leggere File Compressi
Per leggere dati da file precedentemente compressi, si apra il file in modalità lettura ('rb'
). Il valore ritornato da read()
sarà una stringa di byte.
# bz2_file_read.py
import bz2
import io
with bz2.BZ2File('esempio.bz2', 'rb') as input:
with io.TextIOWrapper(input, encoding='utf-8') as dec:
print(dec.read())
Questo esempio legge il file scritto da bz2_file_write.py
dalla sezione precedente. Il file aperto con BZ2File
viene impacchettato con un TextIOWrapper
per decodificare i byte letti in testo Unicode.
$ python3 bz2_file_read.py Il contenuto del file esempio va qui.
Quando si sta leggendo un file, è possibile anche spostarsi al suo interno, e leggere solo parte dei dati.
# bz2_file_seek.py
import bz2
import contextlib
with bz2.BZ2File('esempio.bz2', 'rb') as input:
print('Tutto il file:')
all_data = input.read()
print(all_data)
expected = all_data[5:15]
# mi porto all'inizio
input.seek(0)
# mi sposto in avanti di 5 byte
input.seek(5)
print('A partire dalla posizione 5 per 10 byte:')
partial = input.read(10)
print(partial)
print()
print(expected == partial)
La posizione di seek()
è relativa ai dati non compressi, quindi il chiamante non deve preoccuparsi di sapere che il file è compresso. Questo consente di passare l'istanza di BZ2File
ad una funzione che si attende un normale file non compresso.
$ python3 bz2_file_seek.py Tutto il file: b'Il contenuto del file esempio va qui.\n' A partire dalla posizione 5 per 10 byte: b'ntenuto de' True
Leggere e Scrivere Dati Unicode
Gli esempi precedenti usano BZ2File
direttamente e gestiscono la codifica e decodifica delle stringhe di testo Unicode con un TextIOWrapper
in linea, dove necessario. Questi passi supplementari possono essere evitati usando bz2.open()
, che imposta un io.TextIOWrapper
per gestire la codifica e decodifica automaticamente.
# bz2_unicode.py
import bz2
import os
data = 'Carattere accentato å'
with bz2.open('esempio.bz2', 'wt', encoding='utf-8') as output:
output.write(data)
with bz2.open('esempio.bz2', 'rt', encoding='utf-8') as input:
print('File completo: {}'.format(input.read()))
# Spostamento all'inizio del carattere accentato.
with bz2.open('esempio.bz2', 'rt', encoding='utf-8') as input:
input.seek(20)
print('Un carattere: {}'.format(input.read(1)))
# Spostamento in mezzo al carattere accentato
with bz2.open('esempio.bz2', 'rt', encoding='utf-8') as input:
input.seek(21)
try:
print(input.read(1))
except UnicodeDecodeError:
print('ERRORE: decodifica fallita')
L'handle del file ritornato da opern()
supporta seek()
, ma si raccomanda prudenza in quanto il puntatore del file si sposta per byte e non per caratteri, e potrebbe non trovarsi nel mezzo di un carattere codificato.
$ python3 bz2_unicode.py File completo: Carattere accentato å Un carattere: å ERRORE: decodifica fallita
Comprimere Dati in Rete
L'esempio seguente risponde a richieste che contengono nomi di file scrivendo una versione compressa del file verso il socket usato per comunicare con il client. Ha impostata una spezzettatura artificiale sul posto per illustrare l'accumulo quando i dati passati da compress()
a decompress()
non costituiscono un blocco completo di output compresso o decompresso.
# bz2_server.py
# -*- coding: utf-8 -*-
import bz2
import logging
import socketserver
import binascii
BLOCK_SIZE = 32
class Bz2RequestHandler(socketserver.BaseRequestHandler):
logger = logging.getLogger('Server')
def handle(self):
compressor = bz2.BZ2Compressor()
# Cosa vuole il client?
filename = self.request.recv(1024).decode('utf-8')
self.logger.debug('il client richiede: "%s"', filename)
# Invio di pezzi del file mentre si stanno comprimento
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('ACCUMULO')
# Invia tutti i dati accumulati al compressore
remaining = compressor.flush()
while remaining:
to_send = remaining[:BLOCK_SIZE]
remaining = remaining[BLOCK_SIZE:]
self.logger.debug('SCARICAMENTO %r',
binascii.hexlify(to_send))
self.request.send(to_send)
return
if __name__ == '__main__':
import socket
import sys
from io import StringIO
import threading
logging.basicConfig(level=logging.DEBUG,
format='%(name)s: %(message)s',
)
# Set up a server, running in a separate thread
address = ('localhost', 0) # let the kernel assign a port
server = socketserver.TCPServer(address, Bz2RequestHandler)
ip, port = server.server_address # what port was assigned?
t = threading.Thread(target=server.serve_forever)
t.setDaemon(True)
t.start()
logger = logging.getLogger('Client')
# Connessione al server
logger.info('Contatto il server us %s:%s', ip, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
# Richiesta di un file
requested_file = (sys.argv[0]
if len(sys.argv) > 1
else 'lorem.txt')
logger.debug('invio del nome del file: "%s"', requested_file)
len_sent = s.send(requested_file.encode('utf-8'))
# Riceve una risposta
buffer = StringIO()
decompressor = bz2.BZ2Decompressor()
while True:
response = s.recv(BLOCK_SIZE)
if not response:
break
logger.debug('LETTURA %r', binascii.hexlify(response))
# Include tutti i dati non consumati quando si
# alimenta il decompressore.
decompressed = decompressor.decompress(response)
if decompressed:
logger.debug('DECOMPRESSI %r', decompressed)
buffer.write(decompressed.decode('utf-8'))
else:
logger.debug('ACCUMULO')
full_response = buffer.getvalue()
lorem = open(requested_file, 'rt').read()
logger.debug('la risposta corrisponde al contenuto del file: %s',
full_response == lorem)
# Pulizia
server.shutdown()
server.socket.close()
s.close()
Il programma principale fa partire un server in un thread, combinando SocketServer
e Bz2RequestHandler
quindi apre un socket per comunicare con il server come un client, e richiede il file (predefinito lorem.txt
).
$ python3 bz2_server.py Client: Contatto il server us 127.0.0.1:57563 Client: invio del nome del file: "lorem.txt" Server: il client richiede: "lorem.txt" Server: GREZZI b'Lorem ipsum dolor sit amet, cons' Server: ACCUMULO Server: GREZZI b'ectetuer adipiscing elit.\nDonec ' Server: ACCUMULO Server: GREZZI b'egestas, enim et consectetuer ul' Server: ACCUMULO Server: GREZZI b'lamcorper, lectus ligula rutrum ' Server: ACCUMULO Server: GREZZI b'leo,\na elementum elit tortor eu ' Server: ACCUMULO Server: GREZZI b'quam. Duis tincidunt nisi ut ant' Server: ACCUMULO Server: GREZZI b'e. Nulla\nfacilisi. Sed tristique' Server: ACCUMULO Server: GREZZI b' eros eu libero. Pellentesque ve' Server: ACCUMULO Server: GREZZI b'l\narcu. Vivamus purus orci, iacu' Server: ACCUMULO Server: GREZZI b'lis ac, suscipit sit amet, pulvi' Server: ACCUMULO Server: GREZZI b'nar eu,\nlacus. Praesent placerat' Server: ACCUMULO Server: GREZZI b' tortor sed nisl. Nunc blandit d' Server: ACCUMULO Server: GREZZI b'iam egestas\ndui. Pellentesque ha' Server: ACCUMULO Server: GREZZI b'bitant morbi tristique senectus ' Server: ACCUMULO Server: GREZZI b'et netus et\nmalesuada fames ac t' Server: ACCUMULO Server: GREZZI b'urpis egestas. Aliquam viverra f' Server: ACCUMULO Server: GREZZI b'ringilla\nleo. Nulla feugiat augu' Server: ACCUMULO Server: GREZZI b'e eleifend nulla. Vivamus mauris' Server: ACCUMULO Server: GREZZI b'. Vivamus sed\nmauris in nibh pla' Server: ACCUMULO Server: GREZZI b'cerat egestas. Suspendisse poten' Server: ACCUMULO Server: GREZZI b'ti. Mauris\nmassa. Ut eget velit ' Server: ACCUMULO Server: GREZZI b'auctor tortor blandit sollicitud' Server: ACCUMULO Server: GREZZI b'in. Suspendisse\nimperdiet justo.' Server: ACCUMULO Server: GREZZI b'\n' Server: ACCUMULO Server: SCARICAMENTO b'425a6839314159265359ef75176500004357800010400524074b003ff7ff0040' Server: SCARICAMENTO b'01d1c6820d0988a7e5349e88d000d06a6342298d4d3401a640634c4611a60000' Client: LETTURA b'425a6839314159265359ef75176500004357800010400524074b003ff7ff0040' Server: SCARICAMENTO b'04a6441354f4c9a483d47a87a8306efbf1e657e1e92000a3e1a0eab2c3505d85' Client: ACCUMULO Server: SCARICAMENTO b'acfe24c14151142e5e597e9046c9886d5ab53365e0e9020b30afbc8acbe3be0d' Client: LETTURA b'01d1c6820d0988a7e5349e88d000d06a6342298d4d3401a640634c4611a60000' Server: SCARICAMENTO b'6896aa43cc54e6caeb75c860ba68888294a85badd1d3380a36c16c66ee43dee7' Client: ACCUMULO Server: SCARICAMENTO b'fa24ef3c7690db6d173cd14573633d4854be4bdcc60e119c0cca77ab4d82be33' Client: LETTURA b'04a6441354f4c9a483d47a87a8306efbf1e657e1e92000a3e1a0eab2c3505d85' Server: SCARICAMENTO b'b5e123e2f22dd9eb304f0c48f46f5dffa66ea8d7766e5fea2f79df1d6c790d5c' Client: ACCUMULO Server: SCARICAMENTO b'e94dcb1d5471e1e115f3072d55d8f1887a374be707659a519b2ad7135d551b83' Client: LETTURA b'acfe24c14151142e5e597e9046c9886d5ab53365e0e9020b30afbc8acbe3be0d' Server: SCARICAMENTO b'064d52e690a760a2e2a6c99c6bdc6fbf3b62375a387a3182475f2e57acf8c436' Client: ACCUMULO Server: SCARICAMENTO b'a19f8198554e4cc61d287670a2caa24bbb509a24ab53414bc2ebb75faf05e385' Client: LETTURA b'6896aa43cc54e6caeb75c860ba68888294a85badd1d3380a36c16c66ee43dee7' Server: SCARICAMENTO b'f6dbf76a8dbc68785c9984849525bf270a197f2c929bf3d46e5152dc4ceafaba' Client: ACCUMULO Server: SCARICAMENTO b'28bb702a1a7a52cfb988c58625624bd0c112fb59d14ad2dfb9c245454c33215b' Client: LETTURA b'fa24ef3c7690db6d173cd14573633d4854be4bdcc60e119c0cca77ab4d82be33' Server: SCARICAMENTO b'23916d45992d08456c2aa8c0c8ebb88bf8a0081d76d9edf84ce994f178925732' Client: ACCUMULO Server: SCARICAMENTO b'e6e622de86f2fa1203ec9fc5dc914e14243bdd45d940' Client: LETTURA b'b5e123e2f22dd9eb304f0c48f46f5dffa66ea8d7766e5fea2f79df1d6c790d5c' Client: ACCUMULO Client: LETTURA b'e94dcb1d5471e1e115f3072d55d8f1887a374be707659a519b2ad7135d551b83' Client: ACCUMULO Client: LETTURA b'064d52e690a760a2e2a6c99c6bdc6fbf3b62375a387a3182475f2e57acf8c436' Client: ACCUMULO Client: LETTURA b'a19f8198554e4cc61d287670a2caa24bbb509a24ab53414bc2ebb75faf05e385' Client: ACCUMULO Client: LETTURA b'f6dbf76a8dbc68785c9984849525bf270a197f2c929bf3d46e5152dc4ceafaba' Client: ACCUMULO Client: LETTURA b'28bb702a1a7a52cfb988c58625624bd0c112fb59d14ad2dfb9c245454c33215b' Client: ACCUMULO Client: LETTURA b'23916d45992d08456c2aa8c0c8ebb88bf8a0081d76d9edf84ce994f178925732' Client: ACCUMULO Client: LETTURA b'e6e622de86f2fa1203ec9fc5dc914e14243bdd45d940' Client: DECOMPRESSI b'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.\nDonec egestas, enim et consectetuer ullamcorper, lectus ligula rutrum leo,\na elementum elit tortor eu quam. Duis tincidunt nisi ut ante. Nulla\nfacilisi. Sed tristique eros eu libero. Pellentesque vel\narcu. Vivamus purus orci, iaculis ac, suscipit sit amet, pulvinar eu,\nlacus. Praesent placerat tortor sed nisl. Nunc blandit diam egestas\ndui. Pellentesque habitant morbi tristique senectus et netus et\nmalesuada fames ac turpis egestas. Aliquam viverra fringilla\nleo. Nulla feugiat augue eleifend nulla. Vivamus mauris. Vivamus sed\nmauris in nibh placerat egestas. Suspendisse potenti. Mauris\nmassa. Ut eget velit auctor tortor blandit sollicitudin. Suspendisse\nimperdiet justo.\n' Client: la risposta corrisponde al contenuto del file: True
Vedere anche:
- bz2
- La documentazione della libreria standard per questo modulo.
- bzip2.org
- La pagina home per bzip2.
- zlib
- Il modulo zlib è una interfaccia a più basso livello alla compressione gzip.
- bzip
- Una intefaccia tipo file per file compressi GNU zip
- io
- Costruzioni per creare condutture in input ed output
- Note di portabilità da 2 a 3 per bzip