The Daily Newsstand · Free, Always
Thursday, August 20, 2026

Пишем huntZip — Как я написал библиотеку для архивов на Python

Translate

Как‑то раз мне понадобилось работать с архивами в одном своем проекте, и я знатно прифигел от стандартных инструментов. Возник такой конфуз: для обычного ZIP нужен zipfile, для какого нибудь TAR tarfile, а если понадобится добавить 7z или RAR, то вообще нужны py7zr и rarfile, из за этого код превращается в странный и «не очень красивый» забор из импортов и кусков логики под каждый чих. Меня это ооочень сильно выбесило, и буквально 2–3 дня назад я сел писать свой «комбайн» для архивов, который назвал huntZip.

Какие вообще архивы поддерживаются?

  • ZIP (так же включая Zip64): Поддерживается распаковка и запаковка

  • TAR (а так же его версии GZip, Bzip2, XZ): Полноценная поддержка

  • RAR: В разработке (Попробую добавить запаковку, по поводу распаковки пока не знаю)

Тесты скорости (бенчмарки)

Я решил проверить скорость моей библиотеки по сравнению с zipfile, и пришел к выводу что huntZip быстрее zipfile в 1.26 раз. Все тесты я проводил на Ryzen 5 (Но скорее всего будет так же быстро работать и на других ПК)

  • huntZip: 0.334 сек.

  • zipfile: 0.421 сек.

    Почему так быстро? Дело в том, что я использовал метод f.relative_to(source) и подключил Zip64 через allowZip64=True.

    И вместо старого и уже многим известного метода os.walk, который выдает кучу лишних строк и усложняет код, я использую метод source.rglob("*") из библиотеки pathlib. Он работает как ленивый генератор. Это значит, что Python не загружает весь список файлов папки в память сразу, а перебирает их по одному на лету.

    По поводу f.relative_to(source) — f.relative_to(source) находит относительный путь файла внутри папки. Из‑за этого структура архива получается чистой, без дублирования лишних родительских путей в самом ZIP‑файле.

Как скачать библиотеку?

Скачать вы можете в PyPI по простецкой команде:

pip install huntzip

Либо в разделе Releases на Codeberg

А как вообще работать с библиотекой?

Возьмем в пример ZIP:

from huntzip import pack, unpack

pack("my_project", "backup.zip")

(Для других форматов просто меняем расширение.)

А если захотели распаковать:

unpack("backup.7z", "extracted_ready")

(либо вместо 7z поставьте ваш формат)

Как это устроено внутри?

Код получился очень простым и прозрачным. Я собираю расширения через pathlib и вызываю нужный контекстный менеджер под капотом:

import os
import tarfile
import zipfile
from pathlib import Path
import py7zr


def pack(source_path: str, archive_name: str) -> str:
    source = Path(source_path)
    if not source.exists():
        raise FileNotFoundError(f"Path not found: {source_path}")

    archive = Path(archive_name)
    ext = "".join(archive.suffixes).lower()

    if ext == ".zip":
        with zipfile.ZipFile(archive, "w", zipfile.ZIP_DEFLATED, allowZip64=True) as zipf:
            if source.is_file():
                zipf.write(source, source.name)
            else:
                for f in source.rglob("*"):
                    if f.is_file():
                        zipf.write(f, f.relative_to(source))

    elif ext == ".7z":
        with py7zr.SevenZipFile(archive, "w") as sz:
            if source.is_file():
                sz.write(source, source.name)
            else:
                sz.writeall(source, source.name)

    elif ".tar" in ext or ext.endswith((".gz", ".bz2", ".xz")):
        mode = "w"
        if ext.endswith(".gz"):
            mode = "w:gz"
        elif ext.endswith(".bz2"):
            mode = "w:bz2"
        elif ext.endswith(".xz"):
            mode = "w:xz"

        with tarfile.open(archive, mode) as tar:
            tar.add(source, arcname=source.name)

    else:
        raise ValueError(f"Unsupported format: {ext}")

    return str(archive.resolve())


def unpack(archive_path: str, extract_to: str = ".") -> str:
    archive = Path(archive_path)
    if not archive.exists():
        raise FileNotFoundError(f"Archive not found: {archive_path}")

    target_dir = Path(extract_to)
    target_dir.mkdir(parents=True, exist_ok=True)

    ext = "".join(archive.suffixes).lower()

    if ext == ".zip":
        with zipfile.ZipFile(archive, "r", allowZip64=True) as zipf:
            zipf.extractall(target_dir)

    elif ext == ".7z":
        with py7zr.SevenZipFile(archive, "r") as sz:
            sz.extractall(path=target_dir)

    elif ".tar" in ext or ext.endswith((".gz", ".bz2", ".xz")):
        with tarfile.open(archive, "r:*") as tar:
            try:
                tar.extractall(target_dir, filter='fully_trusted')
            except TypeError:
                tar.extractall(target_dir)

    else:
        raise ValueError(f"Unknown archive format: {ext}")

    return str(target_dir.resolve())

Что дальше, и проблема с RAR

Проект развивается, но я есть юридический тупик с RAR.

Изначально хотел сделать и чтение, и запись. Но формат проприетарный, алгоритм сжатия принадлежит WinRAR. Бесплатно и легально можно только распаковать файлы. Напишешь свою запаковку...И прилетит DMCA, а тем самым репозиторий удалят.

Поэтому планы такие:

  1. RAR только на распаковку: Скоро добавлю распаковку через rarfile. При попытке запаковать в .rar библиотека просто выдаст ошибку.

  2. Индикатор прогресса: Сделаю коллбеки, чтобы при сборке больших архивов в консоли бежала полоса загрузки.

Как помочь проекту?

Код открытый, и я буду рад любой помощи. Если хотите докинуть фичу:

  1. Форкайте репозиторий на Codeberg.

  2. Делайте ветку с вашей фичей.

  3. Пишите тесты.

  4. Присылайте Pull Request.

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.