Working with ZIP Archives Using zipfile
Create, inspect, update, verify, and selectively extract ZIP archives with Python’s standard-library zipfile module.
Intermediate
Python’s zipfile module lets you create, inspect, read, update, and extract ZIP archives. It works below the higher-level archive helpers in shutil: use zipfile when you need to decide which members to write, inspect individual entries, or read a member without first extracting the whole archive.
Choose between zipfile and shutil
shutil.make_archive() and shutil.unpack_archive() are convenient for common whole-directory archive tasks. ZipFile gives finer control over archive modes, member names, compression choices, member metadata, and reading individual members. A ZIP archive is not the same thing as a compressed single file: it is a container with its own member list and metadata.
Create a ZIP archive with member names
Open ZipFile in write mode and add content with writestr() when you already have the content in memory. Each name is stored inside the archive; it does not have to be a path on the current filesystem. Use a with statement so the ZIP directory and other final records are written when the archive closes.
Output
Ready to run.
Write files with archive-relative names
ZipFile.write() copies an existing filesystem file into an archive. Its arcname argument controls the name stored in the archive, which is usually better than preserving a machine-specific absolute path. Prefer short relative names that make sense to someone opening the archive on another computer.
Output
Ready to run.
Inspect archive members
namelist() returns the stored member names. infolist() returns ZipInfo objects with details such as each member’s name, uncompressed size, and compression method. A ZIP file may contain duplicate member names; when that matters, keep and pass the specific ZipInfo object rather than assuming a name identifies exactly one entry.
Output
Ready to run.
Read member bytes and decode text
ZipFile.read() returns bytes, not str. Decode those bytes using the encoding that was used for the text, just as you would when reading a binary file. For larger members, ZipFile.open() provides a file-like stream so the whole decompressed member does not need to be loaded into memory at once.
Output
Ready to run.
Append to an archive and check its integrity
Use mode "a" to add members to an existing ZIP archive. It preserves existing members and appends new entries. testzip() reads members and checks their headers and CRC values, returning the first bad member name or None. This check detects some corruption; it does not establish that an archive is safe or trustworthy.
Output
Ready to run.
Extract archives only when you trust the source
Use extract() or extractall() only after considering where the members will be written and how much data they may expand to. zipfile attempts to sanitize some dangerous member paths, but untrusted ZIP files can still overwrite files or exhaust disk space and memory. Inspect names and sizes first, extract into a new controlled directory, and enforce application-specific limits. Unlike tarfile, ZIP extraction has no tar extraction-filter parameter.
Output
Ready to run.
Choose compression deliberately. ZIP_STORED stores members without compression; ZIP_DEFLATED is broadly supported but needs zlib. Other compression methods may not be available in every Python installation or supported by every archive tool. This lesson uses ZIP_STORED and ZIP_DEFLATED so its examples run in the available local interpreter.
Quick check
Which method adds an in-memory string or bytes value under a chosen member name?
from pathlib import Path
from tempfile import TemporaryDirectory
from zipfile import ZipFile
with TemporaryDirectory() as folder:
archive_path = Path(folder) / "notes.zip"
with ZipFile(archive_path, "w") as archive:
with ZipFile(archive_path) as archive:
assert archive.read("notes.txt") == b"ready"
print("written")
Quick check
Which argument stores a portable member name rather than the original filesystem path?
from pathlib import Path
from tempfile import TemporaryDirectory
from zipfile import ZipFile
with TemporaryDirectory() as folder:
root = Path(folder)
source = root / "input.csv"
source.write_text("id\n1\n", encoding="utf-8")
with ZipFile(root / "data.zip", "w") as archive:
with ZipFile(root / "data.zip") as archive:
assert archive.namelist() == ["tables/input.csv"]
print("portable member path")
Quick check
ZipFile.read() returns bytes. Which operation converts UTF-8 bytes into a Python string?
from pathlib import Path
from tempfile import TemporaryDirectory
from zipfile import ZipFile
with TemporaryDirectory() as folder:
path = Path(folder) / "message.zip"
with ZipFile(path, "w") as archive:
archive.writestr("message.txt", "Hi")
with ZipFile(path) as archive:
message =
assert message == "Hi"
print(message)
Quick check
What does testzip() returning None tell you?
from pathlib import Path
from tempfile import TemporaryDirectory
from zipfile import ZipFile
with TemporaryDirectory() as folder:
path = Path(folder) / "bundle.zip"
with ZipFile(path, "w") as archive:
archive.writestr("one.txt", "1")
with ZipFile(path) as archive:
assert result is None
print("member checks finished")
Quick check
The example archive was created by your own program. Which destination choice keeps extracted files separate from the archive and working files?
from pathlib import Path
from tempfile import TemporaryDirectory
from zipfile import ZipFile
with TemporaryDirectory() as folder:
root = Path(folder)
path = root / "bundle.zip"
with ZipFile(path, "w") as archive:
archive.writestr("docs/readme.txt", "hello")
destination = root / "unpacked"
destination.mkdir()
with ZipFile(path) as archive:
assert (destination / "docs" / "readme.txt").read_text(encoding="utf-8") == "hello"
print("extracted to isolated directory")
Key takeaways
- Use ZipFile when an application needs control over individual ZIP members; use shutil for simpler whole-archive workflows.
- Use write() for an existing file and writestr() for content already in memory. Choose archive-relative member names.
- ZIP member reads return bytes. Decode text explicitly and use open() when you need file-like access to a member.
- testzip() checks member headers and CRCs; it does not prove a ZIP is trusted or safe to extract.
- Inspect untrusted archive names and sizes, use a dedicated extraction directory, and apply limits before writing content to disk.
Keep practising
Practise with the ZIP archive code-completion challenges and the ZIP archive quiz . For current parameters, compression support, and extraction warnings, consult the official Python 3.15 zipfile reference, particularly the sections on decompression pitfalls .
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.