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.
import zipfile
with zipfile.ZipFile("backup.zip", "w") as archive:
archive.write("data.csv")
archive.write("notes.txt")
print("Archive created") # Archive createdZipFile("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.
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:
Added: report.txt
Added: summary.csv
Added: config.jsonFiles are added with their original names by default. Use the arcname parameter to store the file under a different path inside the archive:
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.
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.
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:
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.
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:
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:
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.
import zipfile
with zipfile.ZipFile("compressed.zip", "w", compression=zipfile.ZIP_DEFLATED) as archive:
archive.write("large_data.csv")| Method | Description |
|---|---|
| zipfile.ZIP_STORED | No compression (default) |
| zipfile.ZIP_DEFLATED | Standard deflate compression |
| zipfile.ZIP_BZIP2 | BZIP2 compression |
| zipfile.ZIP_LZMA | LZMA 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.
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.
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:
unknown_file.dat is not a ZIP filePractical 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.
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:
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
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_DEFLATEDfor smaller archives. - Use
Path.rglob()withzipfileto archive entire directory trees.
Frequently Asked Questions
What is the difference between ZipFile.write and ZipFile.writestr?
How do I add files from an entire directory to a ZIP?
Can I password-protect a ZIP file with the zipfile module?
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.
More in this topic
Format Dates and Times in Python
Learn how to use strftime and strptime to format dates as strings and parse date strings into Python datetime objects.
What Is the Python Standard Library?
Learn what the Python standard library is, why it matters, and how to use its built-in modules without installing anything extra.
Encode and Decode Base64 Data in Python
Learn how to use Python's base64 module to encode binary data as text and decode Base64 strings back to bytes.