Автор оригинала: Doug Hellmann.
Цель:
bzip2 сжатие
Модуль bz2
– это интерфейс для библиотеки bzip2, используемый для сжатия данных для хранения или передачи. Предоставляется три API:
- “одноразовые” функции сжатия/декомпрессии для работы с большим объемом данных
- объекты итеративного сжатия/распаковки для работы с потоками данных
- файловый класс, который поддерживает чтение и запись как несжатый файл
Одноразовые операции в памяти
Самый простой способ работать с bz2
– загрузить все данные для сжатия или распаковки в память, а затем использовать compress ()
и decopress ()
, чтобы преобразовать его.
bz2_memory.py
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)
Сжатые данные содержат символы, отличные от ASCII, поэтому перед печатью их необходимо преобразовать в шестнадцатеричное представление. В выходных данных этих примеров шестнадцатеричная версия переформатирована так, чтобы в каждой строке было не более 40 символов.
$ 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.'
Для короткого текста сжатая версия может быть значительно длиннее оригинала. Хотя фактические результаты зависят от входных данных, интересно наблюдать накладные расходы на сжатие.
bz2_lengths.py
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 '')
Строки вывода, заканчивающиеся на *
, показывают точки, в которых сжатые данные длиннее, чем исходные данные.
$ python3 bz2_lengths.py len(data) len(compressed) --------------- --------------- 0 14* 26 62* 52 68* 78 70 104 72
Инкрементное сжатие и декомпрессия
Подход в памяти имеет очевидные недостатки, которые делают его непрактичным для реальных случаев использования. Альтернативой является использование объектов BZ2Compressor
и BZ2Decompressor
для постепенного управления данными, так что весь набор данных не должен помещаться в память.
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('Compressed: {}'.format( binascii.hexlify(compressed))) else: print('buffering...') remaining compressor.flush() print('Flushed: {}'.format(binascii.hexlify(remaining)))
В этом примере небольшие блоки данных считываются из текстового файла и передаются в compress ()
. Компрессор поддерживает внутренний буфер сжатых данных. Поскольку алгоритм сжатия зависит от контрольных сумм и минимальных размеров блоков, компрессор может быть не готов возвращать данные каждый раз, когда он получает дополнительный ввод. Если у него нет готового сжатого блока, он возвращает пустую строку. Когда все данные введены, метод flush ()
заставляет компрессор закрыть последний блок и вернуть остальные сжатые данные.
$ python3 bz2_incremental.py buffering... buffering... buffering... buffering... Flushed: b'425a6839314159265359ba83a48c000014d5800010400504052fa 7fe003000ba9112793d4ca789068698a0d1a341901a0d53f4d1119a8d4c9e812 d755a67c10798387682c7ca7b5a3bb75da77755eb81c1cb1ca94c4b6faf209c5 2a90aaa4d16a4a1b9c167a01c8d9ef32589d831e77df7a5753a398b11660e392 126fc18a72a1088716cc8dedda5d489da410748531278043d70a8a131c2b8adc d6a221bdb8c7ff76b88c1d5342ee48a70a12175074918'
Потоки смешанного контента
BZ2Decompressor
также можно использовать в ситуациях, когда сжатые и несжатые данные смешиваются вместе.
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('Decompressed matches lorem:', decompressed_matches) unused_matches decompressor.unused_data lorem print('Unused data matches lorem :', unused_matches)
После распаковки всех данных атрибут unused_data
содержит все неиспользуемые данные.
$ python3 bz2_mixed.py Decompressed matches lorem: True Unused data matches lorem : True
Запись сжатых файлов
BZ2File
можно использовать для записи и чтения из файлов, сжатых с помощью bzip2, с использованием обычных методов записи и чтения данных.
bz2_file_write.py
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')
Чтобы записать данные в сжатый файл, откройте файл в режиме 'wb'
. В этом примере BZ2File
заключен в оболочку TextIOWrapper
из модуля io для кодирования текста Unicode в байты, подходящие для сжатия.
$ python3 bz2_file_write.py example.bz2: bzip2 compressed data, block size = 900k
Можно использовать разные уровни сжатия, передав аргумент compresslevel
. Допустимые значения находятся в диапазоне от 1
до 9
включительно. Более низкие значения быстрее и приводят к меньшему сжатию. Более высокие значения медленнее и сжимаются сильнее, вплоть до определенного момента.
bz2_file_compresslevel.py
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', compressleveli) as output: with io.TextIOWrapper(output, encoding'utf-8') as enc: enc.write(data) os.system('cksum {}'.format(filename))
Центральный столбец чисел в выводе сценария – это размер в байтах созданных файлов. Для этих входных данных более высокие значения сжатия не всегда окупаются уменьшением объема памяти для тех же входных данных. Результаты будут отличаться для других входов.
$ 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
Экземпляр BZ2File
также включает метод writelines ()
, который можно использовать для записи последовательности строк.
bz2_file_writelines.py
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')
Строки должны заканчиваться символом новой строки, как при записи в обычный файл.
$ 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.
Чтение сжатых файлов
Чтобы прочитать данные из ранее сжатых файлов, откройте файл в режиме чтения ( 'rb'
). Значение, возвращаемое функцией read ()
, будет байтовой строкой.
bz2_file_read.py
import bz2 import io with bz2.BZ2File('example.bz2', 'rb') as input: with io.TextIOWrapper(input, encoding'utf-8') as dec: print(dec.read())
В этом примере выполняется чтение файла, записанного bz2_file_write.py
из предыдущего раздела. BZ2File
заключен в оболочку TextIOWrapper
для декодирования байтов, прочитанных в текст Unicode.
$ python3 bz2_file_read.py Contents of the example file go here.
При чтении файла также можно искать и читать только часть данных.
bz2_file_seek.py
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)
Положение seek ()
относится к несжатым данным, поэтому вызывающему абоненту не нужно знать, что файл данных сжат. Это позволяет передать экземпляр BZ2File
функции, ожидающей обычный несжатый файл.
$ 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
Чтение и запись данных Unicode
В предыдущих примерах непосредственно использовался BZ2File
и управлялось кодирование и декодирование текстовых строк Unicode, встроенных с помощью io.TextIOWrapper
, где это необходимо. Этих дополнительных действий можно избежать, используя bz2.open ()
, который настраивает io.TextIOWrapper
для автоматической обработки кодирования или декодирования.
bz2_unicode.py
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')
Дескриптор файла, возвращаемый open ()
, поддерживает seek ()
, но будьте осторожны, потому что указатель файла перемещается на байты , а не на символы и может оказаться в середине закодированного символа.
$ python3 bz2_unicode.py Full file: Character with an åccent. One character: å ERROR: failed to decode
Сжатие сетевых данных
Код в следующем примере отвечает на запросы, состоящие из имен файлов, записывая сжатую версию файла в сокет, используемый для связи с клиентом. В нем есть несколько искусственных фрагментов, чтобы проиллюстрировать буферизацию, которая происходит, когда данные, переданные в compress ()
или decopress ()
, не приводят к созданию полного блока сжатых или несжатых файлов. выход.
bz2_server.py
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
Основная программа запускает сервер в потоке, объединяющем SocketServer
и Bz2RequestHandler
.
if __name__ '__main__': import socket import sys from io import StringIO import threading logging.basicConfig(levellogging.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(targetserver.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()
Затем он открывает сокет для связи с сервером в качестве клиента и запрашивает файл (по умолчанию – lorem.txt
), который содержит:
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.
Предупреждение
Эта реализация имеет очевидные последствия для безопасности. Не запускайте его на сервере в открытом Интернете или в любой среде, где безопасность может быть проблемой.
Запуск bz2_server.py
производит:
$ 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
Смотрите также
- стандартная библиотека документации для bz2
- bzip2.org – домашняя страница для
bzip2
. - zlib