Python zipimport: Import from ZIP Files

Published on: August 14, 2026
Reading time: 5 minutes
Organized archive binders representing modules imported directly from ZIP files with Python zipimport

Python zipimport lets the import system load modules and packages directly from ZIP archives. Most applications do not instantiate it manually: when a ZIP path appears in sys.path, Python automatically selects a zipimporter through its normal path hooks.

This capability is useful for distributing collections of modules as one file, loading packaged plugins, reducing thousands of small filesystem entries, and understanding how .pyz applications work. Importing from a ZIP does not make third-party code safe. The imported module executes in the current process with the application’s privileges.

How ZIP imports work

A sys.path entry usually points to a directory, but it may also name a ZIP file. The importer searches the archive for .py and .pyc files while respecting package directories.

import sys

sys.path.insert(0, "plugins.zip")
import my_plugin

print(my_plugin.__file__)
# plugins.zip/my_plugin.py

You do not need to write import zipimport for this common case. The import machinery selects the appropriate finder automatically.

Package layout inside the archive

Traditional packages retain their normal structure, including __init__.py where required.

plugins.zip
├── my_plugin/
│   ├── __init__.py
│   ├── commands.py
│   └── validation.py
└── utility.py

After adding the ZIP to the path, statements such as from my_plugin import commands work through the usual import rules.

Use an internal subdirectory as the root

A path entry can point into a directory within the archive:

sys.path.insert(0, "package.zip/lib")
import library

Only the lib/ subtree is then treated as an import root. This layout is useful when the archive also contains documentation, metadata, assets, or command-line files.

Instantiate zipimporter directly

Diagnostic tools and custom loaders can create zipimport.zipimporter explicitly.

from zipimport import zipimporter, ZipImportError

try:
    importer = zipimporter("plugins.zip")
except ZipImportError as exc:
    print(f"Invalid archive: {exc}")
else:
    spec = importer.find_spec("my_plugin")
    print(spec)

The constructor accepts a complete archive path or an internal prefix such as plugins.zip/lib. Invalid input raises ZipImportError, which is also an ImportError.

Prefer find_spec

Old finder methods have been removed. Use find_spec(), which returns a ModuleSpec compatible with importlib.

spec = importer.find_spec("my_plugin.commands")
if spec is None:
    raise ModuleNotFoundError("Plugin was not found")

module = __import__("my_plugin.commands", fromlist=["*"])

In normal application code, a regular import statement remains simpler and less error-prone.

Read source without executing the module

get_source() returns source text when the archive contains it.

source = importer.get_source("my_plugin")
if source is not None:
    print(source[:200])

This avoids executing the module, but the source is still untrusted data. Limit its size and do not expose it publicly without authorization.

To list classes and functions without importing, see the Python pyclbr guide.

Retrieve a code object

get_code() returns the code object associated with a module.

code_object = importer.get_code("my_plugin")
print(code_object.co_filename)

Executing that object with exec() has the same risks as importing it. This API is not a sandbox.

Read auxiliary data

get_data() reads bytes from a path in the archive.

data = importer.get_data(
    "plugins.zip/my_plugin/config.json"
)

For package resources, importlib.resources generally offers a more structured interface. Read the Python importlib.resources guide.

Inspect filenames and packages

get_filename() returns the filename that would become __file__. is_package() determines whether a qualified name represents a package.

print(importer.get_filename("my_plugin"))
print(importer.is_package("my_plugin"))

These methods are useful for code browsers, plugin validators, and import diagnostics.

Native-extension limitation

The ZIP importer handles Python source and bytecode, but it cannot load dynamic extensions such as .so or .pyd directly from an archive. The operating-system loader requires native binaries to exist as real filesystem files.

Install native dependencies in the environment or extract verified, platform-specific binaries to a controlled directory. The Python zipapp guide describes the same limitation for .pyz applications.

Source versus bytecode

If an archive contains only .py files, Python compiles them during import but does not modify the ZIP to insert .pyc files. Repeated imports in new processes can therefore be slower.

Bundled bytecode must match the target interpreter. The format can change across versions, so source is often the more portable choice. Bytecode is not encryption and should not be treated as intellectual-property protection.

Create a ZIP for imports

The standard zipfile module can build an archive:

from pathlib import Path
from zipfile import ZipFile, ZIP_DEFLATED

with ZipFile("plugins.zip", "w", ZIP_DEFLATED) as archive:
    for file in Path("plugins").rglob("*.py"):
        archive.write(file, file.relative_to("plugins"))

Exclude caches, test artifacts, environment files, private keys, and credentials. Normalize archive paths and validate the generated file.

Packaged plugin systems

A plugin service may place signed ZIP files in an approved directory, verify hashes, add one path, and import a known entry point. It should never discover and execute every module automatically.

from importlib import import_module

ALLOWED = {"reports", "export"}

name = validate_requested_name()
if name not in ALLOWED:
    raise PermissionError("Plugin is not authorized")

plugin = import_module(f"plugins.{name}")

An allowlist reduces mistakes but does not isolate a hostile plugin. Run third-party extensions in a separate process, container, or stronger sandbox.

Name collisions and sys.path order

Inserting a ZIP at index zero may shadow trusted modules with matching names. A bundled json.py, for example, could replace the expected standard-library import.

Use organization-specific namespaces, avoid putting untrusted archives first, and log each module’s origin. The Python site guide explains path initialization, while Python pkgutil helps discover modules.

Importer caches

invalidate_caches() clears the importer’s internal archive listing.

importer.invalidate_caches()

If an archive changes during execution, invalidate caches and avoid concurrent replacement. A safer deployment uses immutable, versioned filenames and restarts workers.

Atomic updates

Do not edit a ZIP in place while threads or processes import from it. Create a new archive in a temporary path, verify integrity and signatures, flush the file, and replace the published path atomically. Modules already present in sys.modules remain loaded until explicitly reloaded or the process restarts.

ZIP64 and archive size

Current versions support ZIP64, allowing large archives and many entries. Large capacity is not an invitation to ship unbounded plugin bundles. Huge central directories increase startup time, memory use, and denial-of-service risk.

Security checklist

  • Importing executes code with process privileges.
  • Verify publisher, signature, and hash.
  • Restrict archive roots and qualified module names.
  • Never import an arbitrary user upload directly.
  • Prevent trusted-module shadowing.
  • Limit archive size, entry count, and nesting.
  • Isolate untrusted plugins in another process.
  • Record archive version and imported entry point.

Testing strategy

Test packages, subpackages, source-only archives, bytecode-only archives, internal prefixes, corrupted files, ZIP comments, ZIP64, and name collisions. Verify behavior on all target Python versions and operating systems.

Ensure that native dependencies fail with a clear diagnostic and that the import system never falls back to an unexpected module elsewhere on the path.

Conclusion

Python zipimport integrates ZIP archives into the standard import machinery and lets applications load Python modules without extracting them. It provides access to specs, source, code objects, resource bytes, package status, and filenames.

Use it with deliberate origin policies, path controls, and real isolation for third-party code. Consult the official zipimport documentation and PEP 273 for ZIP imports.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Directory diagram representing site-packages paths and Python site module configuration
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python site: Understand Package Paths

    Learn the Python site module to understand site-packages, user site, .pth files, sitecustomize, usercustomize, virtual environments, and startup flags.

    Ler mais

    Tempo de leitura: 6 minutos
    14/08/2026
    Installer icon representing offline pip bootstrap with Python ensurepip
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python ensurepip: Restore pip Offline

    Learn Python ensurepip to install or restore pip offline, choose the environment, script names, upgrade behavior, and avoid system conflicts.

    Ler mais

    Tempo de leitura: 5 minutos
    14/08/2026
    Software package representing package metadata inspected with Python importlib.metadata
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python importlib.metadata: Package Data

    Learn Python importlib.metadata to inspect installed versions, dependencies, files, metadata, entry points, and import-to-distribution mappings.

    Ler mais

    Tempo de leitura: 5 minutos
    14/08/2026
    Executing code representing modules and paths run with Python runpy
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python runpy: Execute Modules and Paths

    Learn Python runpy to execute modules, scripts, directories, and ZIP files, control namespaces, and avoid security and thread-safety problems.

    Ler mais

    Tempo de leitura: 5 minutos
    13/08/2026
    Software package representing module discovery with Python pkgutil
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python pkgutil: Discover Packages

    Learn Python pkgutil to discover modules, walk packages, resolve objects, extend package paths, and access resources safely.

    Ler mais

    Tempo de leitura: 5 minutos
    13/08/2026
    Binary code network representing the import graph analyzed with Python modulefinder
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python modulefinder: Analyze Imports

    Learn Python modulefinder to map imports, detect missing modules, customize search paths, and audit dependencies with clear limitations.

    Ler mais

    Tempo de leitura: 5 minutos
    13/08/2026