Compress Files with Python `zipfile`

Learn how to use Python's zipfile module to create, extract, and inspect ZIP archives with practical examples.

7 min read

The zipfile module in the Python standard library creates, reads, and extracts ZIP archives. ZIP is the most widely used compressed archive format, supported natively by Windows, macOS, and Linux. Use zipfile to bundle multiple files into a single archive, compress data for transfer, or extract downloaded ZIP files.

pythonpython
import zipfile
 
with zipfile.ZipFile("backup.zip", "w") as archive:
    archive.write("data.csv")
    archive.write("notes.txt")
 
print("Archive created")  # Archive created

ZipFile("backup.zip", "w") opens a new ZIP file for writing. archive.write("data.csv") adds that file to the archive with the same name. The with statement ensures the archive is properly closed.

Creating ZIP archives

A ZIP archive starts out empty until you add files to it one at a time. Open a ZIP file in write mode, then call .write() for each file you want to add.

pythonpython
import zipfile
 
files_to_archive = ["report.txt", "summary.csv", "config.json"]
 
with zipfile.ZipFile("project_backup.zip", "w") as archive:
    for filename in files_to_archive:
        archive.write(filename)
        print(f"Added: {filename}")

The loop prints one confirmation line per file as it gets added to the archive, so you can watch the archive fill up as the script runs instead of waiting silently until it finishes:

texttext
Added: report.txt
Added: summary.csv
Added: config.json

Files are added with their original names by default. Use the arcname parameter to store the file under a different path inside the archive:

pythonpython
import zipfile
 
with zipfile.ZipFile("docs.zip", "w") as archive:
    archive.write("report.txt", arcname="documents/report.txt")
    archive.write("notes.txt", arcname="documents/notes.txt")

Inside docs.zip, both files appear under the documents/ folder.

Adding data from memory

Use .writestr() to add string or byte data directly without needing a file on disk.

pythonpython
import zipfile
from datetime import datetime
 
with zipfile.ZipFile("log_archive.zip", "w") as archive:
    timestamp = datetime.now().isoformat()
    content = f"Generated at {timestamp}\nStatus: OK\n"
    archive.writestr("status.txt", content)

writestr is useful for adding generated reports, serialized data, or content from an API response directly into a ZIP without writing temporary files first and then cleaning them up afterward.

Listing contents of a ZIP

Open a ZIP in read mode and use .namelist() or .infolist() to inspect its contents.

pythonpython
import zipfile
 
with zipfile.ZipFile("project_backup.zip", "r") as archive:
    for name in archive.namelist():
        print(name)
 
    print()
    for info in archive.infolist():
        print(f"{info.filename}: {info.file_size} bytes")

The first loop prints just the names, then a blank line separates it from the second loop, which prints each name again alongside its uncompressed size:

texttext
report.txt
summary.csv
config.json
 
report.txt: 1250 bytes
summary.csv: 3400 bytes
config.json: 210 bytes

.namelist() returns filenames as strings. .infolist() returns ZipInfo objects with metadata: file size, compression method, modification time, and CRC checksum. Reading this metadata does not extract any file content, so it is a cheap way to inspect a large archive before deciding what to do with it.

Extracting files

Extract all files or individual files from a ZIP archive.

pythonpython
import zipfile
 
with zipfile.ZipFile("project_backup.zip", "r") as archive:
    archive.extractall("extracted")
    print("All files extracted to 'extracted/'")

.extractall(path) extracts everything to the given directory, creating it if it does not already exist. To pull out just one member instead of the whole archive, use .extract() with the file's name:

pythonpython
with zipfile.ZipFile("project_backup.zip", "r") as archive:
    archive.extract("report.txt", path="output")

.extract(member, path) extracts one file to the specified directory. To read a file directly into memory without extracting to disk:

pythonpython
with zipfile.ZipFile("project_backup.zip", "r") as archive:
    content = archive.read("config.json")
    print(content.decode())

.read(name) returns the file content as bytes.

Choosing compression

ZIP files support different compression methods. The default is no compression (stored), which is fast but produces large archives. Use ZIP_DEFLATED for standard compression.

pythonpython
import zipfile
 
with zipfile.ZipFile("compressed.zip", "w", compression=zipfile.ZIP_DEFLATED) as archive:
    archive.write("large_data.csv")
MethodDescription
zipfile.ZIP_STOREDNo compression (default)
zipfile.ZIP_DEFLATEDStandard deflate compression
zipfile.ZIP_BZIP2BZIP2 compression
zipfile.ZIP_LZMALZMA compression (best ratio, slower)

ZIP_DEFLATED is the most compatible option. ZIP_BZIP2 and ZIP_LZMA give better compression ratios but may not be supported by all unzip tools, including some built-in operating system archive utilities. For maximum compatibility, use ZIP_DEFLATED.

Archiving an entire directory

Calling .write() once per file works fine for a handful of files, but a real project can have hundreds of files nested across many subfolders. Combine zipfile with pathlib to walk the whole tree and archive it in one pass, without listing every file by hand.

pythonpython
import zipfile
from pathlib import Path
 
def archive_directory(source_dir, output_zip):
    source = Path(source_dir)
    with zipfile.ZipFile(output_zip, "w", compression=zipfile.ZIP_DEFLATED) as archive:
        for file_path in source.rglob("*"):
            if file_path.is_file():
                arcname = file_path.relative_to(source)
                archive.write(file_path, arcname)
                print(f"Added: {arcname}")
 
archive_directory("project_src", "source_backup.zip")

rglob("*") finds every file recursively. relative_to(source) strips the root directory prefix, so files inside the ZIP are stored as src/main.py instead of project_src/src/main.py.

Checking if a name is a ZIP file

Use zipfile.is_zipfile() to check whether a file is a valid ZIP before attempting to open it.

pythonpython
import zipfile
 
filename = "unknown_file.dat"
if zipfile.is_zipfile(filename):
    with zipfile.ZipFile(filename) as archive:
        print(archive.namelist())
else:
    print(f"{filename} is not a ZIP file")

Since unknown_file.dat is not a real ZIP archive, is_zipfile returns False and the else branch runs instead of attempting to open it:

texttext
unknown_file.dat is not a ZIP file

Practical example: selective backup

Backing up an entire project every time is wasteful once it grows large, especially when only a handful of files changed recently. Create a backup that only includes files modified in the last 7 days.

pythonpython
import zipfile
import time
from pathlib import Path
 
def recent_backup(source_dir, output_zip, days=7):
    cutoff = time.time() - (days * 86400)
    source = Path(source_dir)
    recent = [f for f in source.rglob("*") if f.is_file() and f.stat().st_mtime > cutoff]
 
    with zipfile.ZipFile(output_zip, "w", compression=zipfile.ZIP_DEFLATED) as archive:
        for file_path in recent:
            archive.write(file_path, file_path.relative_to(source))
    print(f"Added {len(recent)} files modified in the last {days} days")

Calling the function with a source directory and an output path produces the filtered backup, printing how many files it actually included once the loop finishes:

pythonpython
recent_backup("project", "recent_backup.zip")

file.stat().st_mtime gives the last modification time. Files older than the cutoff are skipped. The archive contains only recently changed files.

Common mistakes

Forgetting to call close() or use a with statement. Without with, you must call archive.close() to finalize the ZIP. An unclosed ZIP may be truncated or corrupted.

Adding directories instead of files. archive.write("mydir") adds an empty directory entry. To archive a directory's contents, iterate over its files with rglob and add each file individually.

Using forward slashes inconsistently in arcname. ZIP format uses forward slashes for directory separators internally. Use pathlib's .as_posix() method or string replacement to ensure forward slashes if you build arcnames manually.

Extracting without checking the contents first. A malicious ZIP can contain paths like ../../etc/passwd that extract outside the target directory (a zip slip attack). This matters most when extracting archives uploaded by users or downloaded from an untrusted source, since a crafted archive could overwrite files well outside the folder you intended. Always inspect the namelist or use extractall with a controlled path.

Rune AI

Rune AI

Key Insights

  • Use ZipFile(name, 'w') to create a new ZIP archive.
  • Use .write(path, arcname=...) to add files and .writestr(name, data) for in-memory data.
  • Use ZipFile(name, 'r') and .extractall() to extract all files from an archive.
  • Use .namelist() to list files and .getinfo(name) to get metadata without extracting.
  • Choose compression with compression=ZIP_DEFLATED for smaller archives.
  • Use Path.rglob() with zipfile to archive entire directory trees.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between ZipFile.write and ZipFile.writestr?

`ZipFile.write(filepath)` adds an existing file from disk to the archive. `ZipFile.writestr(name, data)` adds a string or bytes directly as a new entry without needing a file on disk. Use `write` for existing files and `writestr` for data generated in memory.

How do I add files from an entire directory to a ZIP?

Use `Path.rglob('*')` or `os.walk()` to iterate through the directory tree, then call `zipfile.write(path, arcname=relative_path)` for each file. Pass `arcname` to control the path stored inside the archive, removing the root directory prefix.

Can I password-protect a ZIP file with the zipfile module?

The `zipfile` module supports setting a password with the `pwd` parameter on `extract()` and `read()`, but only for decrypting existing password-protected ZIPs (ZipCrypto). The module cannot create AES-encrypted ZIPs. For creating encrypted archives, use a third-party library or the `pyzipper` package.

Conclusion

The zipfile module handles the most common ZIP archive tasks: creating new archives, adding files and directories, reading file listings, and extracting contents. For simple file bundling and compression, zipfile is the right tool. For features like AES encryption or handling archives in streaming mode without disk I/O, use a third-party library.