Упаковываем и распаковываем файлы, а также управляем файловыми архивами в любых нужных нам форматах с помощью Python.
Стандартная библиотека Python предоставляет модули и инструменты для решения практически любой прикладной задачи, и как вы понимаете, работа с файловыми архивами не является исключением. При этом будь то самые распространенные форматы архивации такие, как tar
или zip
, более специфические – gzip
и bz2
, и совсем экзотические – lzma
, в стандартной библиотеке Python есть все.
Имея в виду такое множество вариантов решения задачи, выбор определенного инструмента для ее решения становится не таким очевидным. Поэтому, чтобы помочь вам ориентироваться в специфике выбора инструментария для работы с файловыми архивами в зависимости от их форматов была написана эта статья. Ниже мы рассмотрим все основные модули и узнаем, как сжимать, распаковывать, проверять, тестировать и защищать наши архивы паролем с использованием стандартной библиотеки Python.
О форматах сжатия
Как упоминалось выше, Python в составе стандартной библиотеки содержит инструменты для работы с (почти) всеми существующими форматами архивации данных, которые только можно вообразить. Итак, для начала разберем основные из них, а затем рассмотрим, как вы можете их в дальнейшем использовать:
-
zlib
– модуль Python, созданный на основе одноименной библиотеки zlib, который предоставляет функционал для работы с файловыми архивами, упакованными с использованием алгоритма Deflate. Он применяется для сжатия и распаковки архивов файлов в форматеzip
,gzip
и многих других. Итак, используя этот модуль, вы, по сути, используетеgzip
совместимый алгоритм сжатия, но с более удобным интерфейсом. Подробнее об этой библиотеке можно найти в Википедии . -
bz2
– модуль, обеспечивающий поддержку алгоритма сжатияbzip2
. Этот алгоритм более эффективен по степени сжатия, чем алгоритм Deflate, но работает достаточно медленно. Еще одним его недостатком является то, что он применим для сжатия только отдельных файлов и поэтому с его использованием нельзя создавать файловый архивы, содержащие файлы и директории с файлами. -
lzma
– это имя алгоритма и модуля Python . Он позволяет обеспечить более высокую степень компрессии, чем большинство «старых» алгоритмов сжатия, а также является алгоритмом, лежащим в основе функционирования утилитыxz
. -
gzip
– это модуль стандартной библиотеки Python, с которым знакомо большинство из нас. Он использует уже упомянутый алгоритм сжатияzlib
и служит интерфейсом, по функционалу схожим с модулямиgzip
иgunzip
. -
shutils
– модуль, который мы обычно не связываем с операциями компрессии и декомпресси файлов, но он предоставляет вспомогательные методы для работы с архивами для управления архивами в форматахtar
,gztar
,zip
,bztar
илиxztar
. -
zipfile
– модуль, который как следует из названия, позволяет работать в Python сzip
архивами. Он предоставляет все необходимые методы для создания, чтения, записи или добавления файлов в ZIP-архивы, а также классы и объекты для упрощения работы с ними. -
tarfile
– этот модуль, как иzipfile
выше, используется для работы сtar
архивами. Он помогает работать с файловыми архивамиgzip
,bz2
а такжеlzma
. А также поддерживает другие функции, которые присутствуют в утилитеtar
, предоставляемой операционной системой Linux.
Упаковываем и распаковываем файлы
И так, у нас в наличии достаточно инструментов (модулей стандартной библиотеки) для работы с архивами в различных форматах. Использование большинства из них обычно не вызывает затруднений, есть также модули с множеством дополнительных специфических функций, но всех объединяет то, что они (как очевидно) включают самые основные (базовые) функции для упаковки (сжатия) и распаковки данных. Итак, давайте более предметно рассмотрим, как с их использованием выполняются эти базовые операции.
И прежде всего это будет модуль zlib
. Он разработан на базе одноименной низкоуровневой библиотеки и обеспечивает удобный интерфейс для ее использования, поэтому сама она не так широко используется. Рассмотрим базовые операции упаковку/распаковку файлов:
import zlib, sys filename_in = "data" filename_out = "compressed_data" with open(filename_in, mode="rb") as fin, open(filename_out, mode="wb") as fout: data = fin.read() compressed_data = zlib.compress(data, zlib.Z_BEST_COMPRESSION) print(f"Original size: {sys.getsizeof(data)}") # Original size: 1000033 print(f"Compressed size: {sys.getsizeof(compressed_data)}") # Compressed size: 1024 fout.write(compressed_data) with open(filename_out, mode="rb") as fin: data = fin.read() compressed_data = zlib.decompress(data) print(f"Compressed size: {sys.getsizeof(data)}") # Compressed size: 1024 print(f"Decompressed size: {sys.getsizeof(compressed_data)}") # Decompressed size: 1000033
В примере кода выше в качестве “подопытного” мы используем файл с именем data
. В общем случае конечно же можно взять любой файл, но, например, в ОС Linux можно сгенерировать его с помощью команды head -c 1MB /zero > data
. В результате получаем файл с размером 1 МБ и бинарным содержимым в виде нулей. Далее мы открываем и считываем его в память, а затем используем функцию compress
для создания сжатых данных, которые затем записываются в выходной файл compressed_data
. Чтобы продемонстрировать то, что мы можем восстановить данные обратно, мы снова открываем сжатый файл и используем для его обработки функцию decompress
. Операторы print
выводят в терминале информацию, что размеры как сжатых, так и распакованных файлов данных совпадают.
Следующий формат архивации и модуль, который вы можете применить на практике – это bz2
. Его можно использовать очень похожим образом как и zlib
в примере выше:
import bz2, os, sys filename_in = "data" filename_out = "compressed_data.bz2" with open(filename_in, mode="rb") as fin, bz2.open(filename_out, "wb") as fout: fout.write(fin.read()) print(f"Uncompressed size: {os.stat(filename_in).st_size}") # Uncompressed size: 1000000 print(f"Compressed size: {os.stat(filename_out).st_size}") # Compressed size: 48 with bz2.open(filename_out, "rb") as fin: data = fin.read() print(f"Decompressed size: {sys.getsizeof(data)}") # Decompressed size: 1000033
Неудивительно, что интерфейс этих модулей в значительной степени идентичен. Поэтому, чтобы показать что-то поинтереснее, в приведенном выше примере мы упростили (сократили) код сжатия прочитанного содержимого и создания нового файла практически до одной строки, а также использовали функцию os.stat
для проверки размера файлов.
Последний из рассмотренных в этой статье модулей является низкоуровневый lzma
. И пять же, чтобы продемонстрировать другие практики написания кода, давайте, на этот раз, сделаем инкрементное (последовательное) сжатие файла блоками заданного размера:
import lzma, os lzc = lzma.LZMACompressor() # cat /usr/share/dict/words | sort -R | head -c 1MB > data filename_in = "data" filename_out = "compressed_data.xz" with open(filename_in, mode="r") as fin, open(filename_out, "wb") as fout: for chunk in fin.read(1024): compressed_chunk = lzc.compress(chunk.encode("ascii")) fout.write(compressed_chunk) fout.write(lzc.flush()) print(f"Uncompressed size: {os.stat(filename_in).st_size}") # Uncompressed size: 972398 print(f"Compressed size: {os.stat(filename_out).st_size}") # Compressed size: 736 with lzma.open(filename_out, "r") as fin: words = fin.read().decode("utf-8").split() print(words[:5]) # ['dabbing', 'hauled', "seediness's", 'Iroquoian', 'vibe']
Как и в примерах выше, мы начинаем с создания исходного файла, состоящего из набора слов, извлеченных из словаря в /usr/share/dict/words
. Это сделано для того, чтобы мы смогли впоследствии проверить, что распакованные данные идентичны оригиналу.
Затем открываем файл с исходными данными и новый для вывода результата сжатия его содержимого, как в предыдущих примерах. Однако на этот раз мы перебираем содержимое 1024-битными блоками chunk
и сжимаем их с помощью метода LZMACompressor.compress
. Далее сжатые данные записываются в выходной файл. После того, как весь исходный файл будет прочитан по частям и сжат, нам нужно вызвать функцию flush
, чтобы завершить процесс сжатия и удалить все оставшиеся данные из буфера объекта «компрессора» lzc
.
Чтобы убедиться, что все работает так, как нам надо, открываем и распаковываем файл обычным способом, а затем выводим в окно терминала пару слов из файла.
Переходим к модулям более высокого уровня – давайте теперь используем модуль gzip
для тех же задач:
import os, sys, shutil, gzip filename_in = "data" filename_out = "compressed_data.tar.gz" with open(filename_in, "rb") as fin, gzip.open(filename_out, "wb") as fout: # Reads the file by chunks to avoid exhausting memory shutil.copyfileobj(fin, fout) print(f"Uncompressed size: {os.stat(filename_in).st_size}") # Uncompressed size: 1000000 print(f"Compressed size: {os.stat(filename_out).st_size}") # Compressed size: 1023 with gzip.open(filename_out, "rb") as fin: data = fin.read() print(f"Decompressed size: {sys.getsizeof(data)}") # Decompressed size: 1000033
В этом примере мы объединили возможности модулей gzip
и shutils
. И может показаться, что ранее мы делали те же базовые операции сжатие/распаковка, как в примерах выше с модулями zlib
и bz2
. Но благодаря функции shutil.copyfileobj
мы можем производить поблочное инкрементное сжатие файла без необходимости осуществлять непосредственно перебор блоков данных в цикле, как мы это делали в примере использования модуля lzma
.
Одним из преимуществ модуля gzip
является то, что он предоставляет интерфейс для управления работой модуля из командной строки. И это работает не только в Linux, модули gzip
и gunzip
интегрированы непосредственно в установочные пакеты Python, и могут вызываться из командной сроки следующим образом:
python3 -m gzip -h usage: gzip.py [-h] [--fast | --best | -d] [file [file ...]] ... ls -l data* -rw-rw-r-- 1 martin martin 1000000 aug 22 18:48 data # Use fast compression on file "data" python3 -m gzip --fast data # File named "data.gz" was generated: ls -l data* -rw-rw-r-- 1 martin martin 1000000 aug 22 18:48 data -rw-rw-r-- 1 martin martin 1008 aug 22 20:50 data.gz
Берем Большой молоток
Если вам удобнее использовать форматы архивов zip
или tar
, то в этот раздел вам будет интересен. В нем мы подробнее рассмотрим работу с соответствующими модулями. Помимо основных операций сжатия/распаковки, модули zip
или tar
предоставляют другие весьма полезные методы, например, такие как проверка контрольных сумм, использование паролей или получение списка файлов в архиве (без его распаковки). Итак, давайте посмотрим их в действии.
import zipfile # shuf -n5 /usr/share/dict/words > words.txt files = ["words1.txt", "words2.txt", "words3.txt", "words4.txt", "words5.txt"] archive = "archive.zip" password = b"verysecret" with zipfile.ZipFile(archive, "w") as zf: for file in files: zf.write(file) zf.setpassword(password) with zipfile.ZipFile(archive, "r") as zf: crc_test = zf.testzip() if crc_test is not None: print(f"Bad CRC or file headers: {crc_test}") info = zf.infolist() # also zf.namelist() print(info) # See all attributes at https://docs.python.org/3/library/zipfile.html#zipinfo-objects # [, # , # ... ] file = info[0] with zf.open(file) as f: print(f.read().decode()) # Olav # teakettles # ... zf.extract(file, "/tmp", pwd=password) # также как и zf.extractall()
Это довольно большой фрагмент кода, но он наглядно демонстрирует наиболее важные возможности модуля zipfile
. В примере мы создаем ZIP-архив с помощью диспетчера контекста ZipFile
, использующегося в режиме записи "w"
(write), а затем в созданный архив добавляем файлы. Как вы можете заметить, нам не нужно предварительно открывать файлы, которые добавляем, и считывать куда-либо их содержимое. Все, что нам нужно сделать, это вызвать метод write
и передать в него имя добавляемого файла (путь к нему). После добавления файлов в архив устанавливаем для него пароль с помощью метода setpassword
.
Далее, чтобы продемонстрировать, что все сработало так как надо, открываем архив. Перед чтением файлов из него с помощью метода testzip
проверяем соответствие их контрольной суммы CRC и значения, указанного в заголовке. Затем используя метод infolist
получаем информацию о файлах, находящихся в архиве. В этом примере мы просто выводим в терминале список объектов типа ZipInfo
, но вы также можете получить значение их атрибутов: получить CRC, размер, параметры сжатия и т.д.
После проверки файлов в архиве открываем и прочитаем один из них, а затем выведем его содержимое в терминале. Мы видим, что его содержимое соответствует ожидаемому, поэтому можем продолжить и извлечь его в новый файл по заданному пути /tmp/
.
Помимо создания архивов и чтения файлов из них, модуль zipfile
позволяет добавлять файлы к существующим архивам. Для этого все, что нам нужно сделать – это изменить режим доступа к архиву на "a"
(append):
with zipfile.ZipFile(archive, "a") as zf: zf.write("words6.txt") print(zf.namelist()) # ['words1.txt', 'words2.txt', 'words3.txt', 'words4.txt', 'words5.txt', 'words6.txt']
Как и в случае с модулем gzip
, модули zipfile
и tarfile
также предоставляют интерфейс командной строки. Чтобы выполнить в терминале простейшие операции архивирования и распаковки, можно воспользоваться следующими приемами:
python3 -m zipfile -c arch.zip words1.txt words2.txt # архивируем python3 -m zipfile -t arch.zip # проверяем Done testing python3 -m zipfile -e arch.zip /tmp # распаковываем ls /tmp/words* /tmp/words1.txt /tmp/words2.txt
И последнее, но не менее важное. Рассмотрим подробнее модуль tarfile
. Синтаксис его использования схож с zipfile
, но он предоставляет некоторые дополнительные функции:
import tarfile files = ["words1.txt", "words2.txt", "words3.txt", "words4.txt"] archive = "archive.tar.gz" with tarfile.open(archive, "w:gz") as tar: for file in files: tar.add(file) # выможете также добавить директории, ее содержимое рекурсивно также будет добавлено в архив, включая ссылки symlink print(f"archive contains: {tar.getmembers()}") # [, # # ... ] info = tar.gettarinfo("words1.txt") # Other Linux attributes - https://docs.python.org/3/library/tarfile.html#tarinfo-objects print(f"{tar.name} contains {info.name} with permissions {oct(info.mode)[-3:]}, size: {info.size} and owner: {info.uid}:{info.gid}") # .../archive.tar содержит words1.txt со следующими полномочиями доступа 644, size: 37 and owner: 500:500 def change_permissions(tarinfo): tarinfo.mode = 0o100600 # -rw-------. return tarinfo tar.add("words5.txt", filter=change_permissions) tar.list() # -rw-r--r-- martin/martin 37 2021-08-23 09:01:56 words1.txt # -rw-r--r-- martin/martin 47 2021-08-23 09:02:06 words2.txt # ... # -rw------- martin/martin 42 2021-08-23 09:02:22 words5.txt
Мы начинаем с базовой операции создания архива, но, как можем заметить, здесь мы используем режим доступа "w:gz"
, который указывает, что мы хотим использовать определенный тип сжатия GZ. После этого мы добавляем все необходимые файлы в архив. С помощью модуля tarfile
мы также можем добавлять в архив символические ссылки или целые директории с содержимым, которое будет добавлено в архив рекурсивно.
Затем, чтобы убедиться, что все файлы действительно присутствуют в архиве, мы используем метод getmembers
. Чтобы получить информацию об отдельных файлах, находящихся в архиве, используется метод gettarinfo
, также он предоставляет содержимое всех атрибутов файлов.
Модуль tarfile
содержит еще одну интересную функцию, которую мы не увидим у других, ранее рассмотренных модулей, а именно возможность изменять атрибуты файлов при их добавлении в архив. В приведенном выше фрагменте кода мы изменяем режим доступа к файлу, передав в качестве параметра функцию filter
, которая изменяет значение соответствующего атрибута файла TarInfo.mode
. Это значение должно быть представлено в виде восьмеричного числа, в нашем примере это 0o100600
. Вызов функции для добавляемого в архив файла устанавливает его права доступа 0600
или -rw-------
.
Чтобы получить полный список файлов после добавления их в архив, вызываем метод list
, который выводит в терминале результат, аналогичный команде ls -l
.
Последнее, что мы еще можем сделать с tar
архивом – это открыть его и извлечь содержимое. Для этого открываем его в режиме "r:gz"
, явно указывая тип сжатия. Получаем объект member
, который содержит информацию о всех файлах, упакованных в архиве. Используя строковое значение, соответствующее имени файла "words3.txt"
, проверяем, действительно ли он находится в архиве, и извлекаем его в указанное место:
with tarfile.open(archive, "r:gz") as tar: member = tar.getmember("words3.txt") if member.isfile(): tar.extract(member, "/tmp/")
Заключение
Как видите, модули Python предоставляют множество функций для работы с архивами, как низкого, так и высокого уровня, как общие (базовые), так и весьма специфические. Что вы выберете, зависит от конкретной задачи и требований к результату ее решения. В основном я бы рекомендовал использовать модули общего назначения zipfile
или tarfile
, и прибегать к lzma
и ему подобным только в том случае, если вам это действительно нужно.
Я попытался охватить все наиболее распространенные варианты использования этих модулей, чтобы дать вам обзор их возможностей, но, очевидно, в каждом из этих модулей есть гораздо больше полезных функций, объектов, атрибутов и т. д., Поэтому ознакомьтесь с их документацией.
Полезная информация
Спасибо за полезный материал.
Благодарю вас, очень приятно было прочитать, и сделать для себя определённые выводы.
Отличная статья, спасибо.
Здравствуйте спасибо за информацию.
За предоставленную информацию благодарим.