bz2 — Compresión bzip2¶
Propósito: | Compresión bzip2 |
---|
El módulo bz2
es una interfaz para la biblioteca bzip2, utilizada para
comprimir datos para su almacenamiento o transmisión. Están previstas tres
interfaces de programación:
- Funciones de compresión/descompresión «de una acción» para operar en un blob de datos
- Objetos iterativos de compresión/descompresión para trabajar con flujos de datos
- una clase similar a un archivo que admite la lectura y la escritura como con un archivo sin comprimir
Operaciones de una acción en la memoria¶
La forma más sencilla de trabajar con bz2
es cargar todos los datos a
comprimirse o descomprimirse en la memoria, y luego usar compress()
y
decompress()
para transformarlos.
import bz2
import binascii
original_data = b'This is the original text.'
print('Original : {} bytes'.format(len(original_data)))
print(original_data)
print()
compressed = bz2.compress(original_data)
print('Compressed : {} bytes'.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('Decompressed : {} bytes'.format(len(decompressed)))
print(decompressed)
Los datos comprimidos contienen caracteres no ASCII, por lo que deben ser convertidos a su representación hexadecimal antes de que se puedan imprimir. En la salida de estos ejemplos, la versión hexadecimal es reformateada para tener como máximo 40 caracteres en cada línea.
$ python3 bz2_memory.py
Original : 26 bytes
b'This is the original text.'
Compressed : 62 bytes
b'425a683931415926535916be35a6000002938040'
b'01040022e59c402000314c000111e93d434da223'
b'028cf9e73148cae0a0d6ed7f17724538509016be'
b'35a6'
Decompressed : 26 bytes
b'This is the original text.'
Para texto corto, la versión comprimida puede ser significativamente más larga que el original. Mientras que los resultados reales dependen de los datos de entrada, Es interesante observar la sobrecarga de compresión.
import bz2
original_data = b'This is the original text.'
fmt = '{:>15} {:>15}'
print(fmt.format('len(data)', '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 '')
Las líneas de salida que terminan con *
muestran los puntos donde los datos
comprimidos son más grandes que la entrada en bruto.
$ python3 bz2_lengths.py
len(data) len(compressed)
--------------- ---------------
0 14*
26 62*
52 68*
78 70
104 72
Compresión y descompresión incremental¶
El enfoque en memoria tiene inconvenientes obvios que lo hacen poco práctico
para casos de uso en el mundo real. La alternativa es usar los objetos
BZ2Compressor
y BZ2Decompressor
para manipular los datos de manera
incremental donde no es necesario que todo el conjunto de datos encaje en la
memoria.
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('Compressed: {}'.format(
binascii.hexlify(compressed)))
else:
print('buffering...')
remaining = compressor.flush()
print('Flushed: {}'.format(binascii.hexlify(remaining)))
Este ejemplo lee pequeños bloques de datos de un archivo de texto plano y se
los pasa a compress()
. El compresor mantiene un buffer interno de datos
comprimidos. Dado que el algoritmo de compresión depende de sumas de
comprobación y tamaños de bloque mínimos, el compresor puede no estar listo
para devolver datos cada vez que recibe más entrada. Si no tiene listo todo el
bloque comprimido, devuelve una cadena vacía. Cuando todos los datos están
presentes, el método flush()
fuerza al compresor a cerrar el bloque final y
devolver el resto de los datos comprimidos.
$ python3 bz2_incremental.py
buffering...
buffering...
buffering...
buffering...
Flushed: b'425a6839314159265359ba83a48c000014d5800010400504052fa
7fe003000ba9112793d4ca789068698a0d1a341901a0d53f4d1119a8d4c9e812
d755a67c10798387682c7ca7b5a3bb75da77755eb81c1cb1ca94c4b6faf209c5
2a90aaa4d16a4a1b9c167a01c8d9ef32589d831e77df7a5753a398b11660e392
126fc18a72a1088716cc8dedda5d489da410748531278043d70a8a131c2b8adc
d6a221bdb8c7ff76b88c1d5342ee48a70a12175074918'
Flujos de contenido mixto¶
BZ2Decompressor
también se puede utilizar en situaciones donde los datos
comprimidos y sin comprimir están mezclados juntos.
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('Decompressed matches lorem:', decompressed_matches)
unused_matches = decompressor.unused_data == lorem
print('Unused data matches lorem :', unused_matches)
Después de descomprimir todos los datos, el atributo unused_data
contiene
cualquier dato no utilizado.
$ python3 bz2_mixed.py
Decompressed matches lorem: True
Unused data matches lorem : True
Escribir archivos comprimidos¶
BZ2File
puede usarse para escribir y leer desde archivos comprimidos con
bzip2 utilizando los métodos habituales para escribir y leer datos.
import bz2
import io
import os
data = 'Contents of the example file go here.\n'
with bz2.BZ2File('example.bz2', 'wb') as output:
with io.TextIOWrapper(output, encoding='utf-8') as enc:
enc.write(data)
os.system('file example.bz2')
Para escribir datos en un archivo comprimido, abre el archivo con el modo
'wb'
. Este ejemplo envuelve el BZ2File
con un TextIOWrapper
del
módulo io
para codificar texto Unicode a bytes adecuados para la
compresión.
$ python3 bz2_file_write.py
example.bz2: bzip2 compressed data, block size = 900k
Se pueden usar diferentes niveles de compresión al pasar un argumento
compresslevel
. Los valores válidos van desde 1
hasta 9
, inclusive.
Los valores mas bajos son más rápidos y dan como resultado una menor compresión.
Los valores más altos son más lentos y comprimen más, hasta cierto punto.
import bz2
import io
import os
data = open('lorem.txt', 'r', encoding='utf-8').read() * 1024
print('Input contains {} bytes'.format(
len(data.encode('utf-8'))))
for i in range(1, 10):
filename = 'compress-level-{}.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 columna central de números en la salida del script es el tamaño en bytes de los archivos producidos. Para estos datos de entrada, los valores de compresión mayores no siempre se pagan en un espacio de almacenamiento reducido. para los mismos datos de entrada. Los resultados variarán para otros entradas.
$ python3 bz2_file_compresslevel.py
3018243926 8771 compress-level-1.bz2
1942389165 4949 compress-level-2.bz2
2596054176 3708 compress-level-3.bz2
1491394456 2705 compress-level-4.bz2
1425874420 2705 compress-level-5.bz2
2232840816 2574 compress-level-6.bz2
447681641 2394 compress-level-7.bz2
3699654768 1137 compress-level-8.bz2
3103658384 1137 compress-level-9.bz2
Input contains 754688 bytes
Una instancia BZ2File
también incluye un método writelines()
que se
puede utilizar para escribir una secuencia de cadenas de texto.
import bz2
import io
import itertools
import os
data = 'The same line, over and over.\n'
with bz2.BZ2File('lines.bz2', 'wb') as output:
with io.TextIOWrapper(output, encoding='utf-8') as enc:
enc.writelines(itertools.repeat(data, 10))
os.system('bzcat lines.bz2')
Las líneas deben terminar en un carácter de nueva línea, como cuando se escriben en un archivo regular.
$ python3 bz2_file_writelines.py
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
The same line, over and over.
Leer archivos comprimidos¶
Para volver a leer datos de archivos previamente comprimidos, abre el archivo
con modo de lectura ('rb'
). El valor devuelto desde read()
será una
cadena de bytes.
import bz2
import io
with bz2.BZ2File('example.bz2', 'rb') as input:
with io.TextIOWrapper(input, encoding='utf-8') as dec:
print(dec.read())
Este ejemplo lee el archivo escrito por bz2_file_write.py
de la sección
previa. El BZ2File
está envuelto con un TextIOWrapper
para decodificar
bytes leídos a texto Unicode.
$ python3 bz2_file_read.py
Contents of the example file go here.
Al leer un archivo, también es posible buscar y leer solo una parte de los datos.
import bz2
import contextlib
with bz2.BZ2File('example.bz2', 'rb') as input:
print('Entire file:')
all_data = input.read()
print(all_data)
expected = all_data[5:15]
# rewind to beginning
input.seek(0)
# move ahead 5 bytes
input.seek(5)
print('Starting at position 5 for 10 bytes:')
partial = input.read(10)
print(partial)
print()
print(expected == partial)
La posición seek()
es relativa a los datos sin comprimir, por lo que la
persona que llama no necesita saber que el archivo de datos está comprimido.
Esto permite que una instancia BZ2File
se pase a una función que espera un
archivo normal sin comprimir.
$ python3 bz2_file_seek.py
Entire file:
b'Contents of the example file go here.\n'
Starting at position 5 for 10 bytes:
b'nts of the'
True
Leer y escribir de datos Unicode¶
Los ejemplos anteriores usaron BZ2File
directamente y manejaron la
codificación y decodificación de cadenas de texto Unicode en línea con un
io.TextIOWrapper
, cuando era necesario. Estos pasos adicionales pueden ser
evitados usando bz2.open()
, que configura un io.TextIOWrapper
para
manejar la codificación o decodificación automáticamente.
import bz2
import os
data = 'Character with an åccent.'
with bz2.open('example.bz2', 'wt', encoding='utf-8') as output:
output.write(data)
with bz2.open('example.bz2', 'rt', encoding='utf-8') as input:
print('Full file: {}'.format(input.read()))
# Move to the beginning of the accented character.
with bz2.open('example.bz2', 'rt', encoding='utf-8') as input:
input.seek(18)
print('One character: {}'.format(input.read(1)))
# Move to the middle of the accented character.
with bz2.open('example.bz2', 'rt', encoding='utf-8') as input:
input.seek(19)
try:
print(input.read(1))
except UnicodeDecodeError:
print('ERROR: failed to decode')
El identificador de archivo devuelto por open()
admite seek()
, pero ten
cuidado porque el puntero del archivo se mueve por bytes no caracteres y
puede terminar en medio de un carácter codificado.
$ python3 bz2_unicode.py
Full file: Character with an åccent.
One character: å
ERROR: failed to decode
Comprimir datos de red¶
El código del siguiente ejemplo responde a solicitudes que consisten en nombres
de archivos que escriben una versión comprimida del archivo en el socket que se
utiliza para comunicarse con el cliente. Tiene alguna fragmentación
artificial para ilustrar el almacenamiento en buffer que se produce cuando los
datos pasados a compress()
o decompress()
no dan como resultado un
bloque completo de salida comprimida o sin comprimir.
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()
# Find out what file the client wants
filename = self.request.recv(1024).decode('utf-8')
self.logger.debug('client asked for: "%s"', filename)
# Send chunks of the file as they are compressed
with open(filename, 'rb') as input:
while True:
block = input.read(BLOCK_SIZE)
if not block:
break
self.logger.debug('RAW %r', block)
compressed = compressor.compress(block)
if compressed:
self.logger.debug(
'SENDING %r',
binascii.hexlify(compressed))
self.request.send(compressed)
else:
self.logger.debug('BUFFERING')
# Send any data being buffered by the compressor
remaining = compressor.flush()
while remaining:
to_send = remaining[:BLOCK_SIZE]
remaining = remaining[BLOCK_SIZE:]
self.logger.debug('FLUSHING %r',
binascii.hexlify(to_send))
self.request.send(to_send)
return
El programa principal inicia un servidor en un hilo, combinando
SocketServer
y Bz2RequestHandler
.
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')
# Connect to the server
logger.info('Contacting server on %s:%s', ip, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))
# Ask for a file
requested_file = (sys.argv[0]
if len(sys.argv) > 1
else 'lorem.txt')
logger.debug('sending filename: "%s"', requested_file)
len_sent = s.send(requested_file.encode('utf-8'))
# Receive a response
buffer = StringIO()
decompressor = bz2.BZ2Decompressor()
while True:
response = s.recv(BLOCK_SIZE)
if not response:
break
logger.debug('READ %r', binascii.hexlify(response))
# Include any unconsumed data when feeding the
# decompressor.
decompressed = decompressor.decompress(response)
if decompressed:
logger.debug('DECOMPRESSED %r', decompressed)
buffer.write(decompressed.decode('utf-8'))
else:
logger.debug('BUFFERING')
full_response = buffer.getvalue()
lorem = open(requested_file, 'rt').read()
logger.debug('response matches file contents: %s',
full_response == lorem)
# Clean up
server.shutdown()
server.socket.close()
s.close()
A continuación, abre un socket para comunicarse con el servidor como cliente, y
solicita el archivo (por defecto lorem.txt
) que contiene:
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec
egestas, enim et consectetuer ullamcorper, lectus ligula rutrum leo,
a elementum elit tortor eu quam. Duis tincidunt nisi ut ante. Nulla
facilisi.
Advertencia
This implementation has obvious security implications. Do not run it on a server on the open Internet or in any environment where security might be an issue.
Ejecutar bz2_server.py
produce:
$ python3 bz2_server.py
Client: Contacting server on 127.0.0.1:57364
Client: sending filename: "lorem.txt"
Server: client asked for: "lorem.txt"
Server: RAW b'Lorem ipsum dolor sit amet, cons'
Server: BUFFERING
Server: RAW b'ectetuer adipiscing elit. Donec\n'
Server: BUFFERING
Server: RAW b'egestas, enim et consectetuer ul'
Server: BUFFERING
Server: RAW b'lamcorper, lectus ligula rutrum '
Server: BUFFERING
Server: RAW b'leo,\na elementum elit tortor eu '
Server: BUFFERING
Server: RAW b'quam. Duis tincidunt nisi ut ant'
Server: BUFFERING
Server: RAW b'e. Nulla\nfacilisi.\n'
Server: BUFFERING
Server: FLUSHING b'425a6839314159265359ba83a48c000014d5800010400
504052fa7fe003000ba'
Server: FLUSHING b'9112793d4ca789068698a0d1a341901a0d53f4d1119a8
d4c9e812d755a67c107'
Client: READ b'425a6839314159265359ba83a48c000014d58000104005040
52fa7fe003000ba'
Server: FLUSHING b'98387682c7ca7b5a3bb75da77755eb81c1cb1ca94c4b6
faf209c52a90aaa4d16'
Client: BUFFERING
Server: FLUSHING b'a4a1b9c167a01c8d9ef32589d831e77df7a5753a398b1
1660e392126fc18a72a'
Client: READ b'9112793d4ca789068698a0d1a341901a0d53f4d1119a8d4c9
e812d755a67c107'
Server: FLUSHING b'1088716cc8dedda5d489da410748531278043d70a8a13
1c2b8adcd6a221bdb8c'
Client: BUFFERING
Server: FLUSHING b'7ff76b88c1d5342ee48a70a12175074918'
Client: READ b'98387682c7ca7b5a3bb75da77755eb81c1cb1ca94c4b6faf2
09c52a90aaa4d16'
Client: BUFFERING
Client: READ b'a4a1b9c167a01c8d9ef32589d831e77df7a5753a398b11660
e392126fc18a72a'
Client: BUFFERING
Client: READ b'1088716cc8dedda5d489da410748531278043d70a8a131c2b
8adcd6a221bdb8c'
Client: BUFFERING
Client: READ b'7ff76b88c1d5342ee48a70a12175074918'
Client: DECOMPRESSED b'Lorem ipsum dolor sit amet, consectetuer
adipiscing elit. Donec\negestas, enim et consectetuer ullamcorpe
r, lectus ligula rutrum leo,\na elementum elit tortor eu quam. D
uis tincidunt nisi ut ante. Nulla\nfacilisi.\n'
Client: response matches file contents: True
Ver también
- Standard library documentation for bz2
- bzip2.org – La página de inicio de
bzip2
. zlib
– El módulozlib
para compresión GNU zip.gzip
– Una interfaz similar a un archivo para archivos comprimidos en GNU zip.io
– Bloques de construcción para la creación de pipes de entrada y salida- Notas para portar Python 2 a 3 para bz2