Python Zipfile: Compress and Extract Archives
Python's built-in zipfile module provides a complete
toolkit for manipulating ZIP archives directly within your applications
without external dependencies. This article explains how
zipfile processes data under the hood, how it implements
compression and decompression algorithms through standard libraries, and
how to programmatically create and unpack ZIP files using pure
Python.
The Architecture of a ZIP File
To understand how zipfile operates, you must understand
the structure of a standard ZIP archive. Unlike tarballs
(.tar.gz), which compress an entire collection of files as
a single stream, a ZIP file compresses each file individually and stores
them alongside metadata headers.
A standard ZIP archive consists of:
- Local File Headers and Data: Each archived file has a preceding local header containing metadata (file name, uncompressed size, compressed size, timestamp) followed immediately by the compressed data block.
- Central Directory: Located toward the end of the archive, this table lists all contained files, their exact byte offsets, and storage attributes.
- End of Central Directory (EOCD) Record: The final bytes of the file, which mark the boundary and point to the beginning of the Central Directory.
Because the master index resides at the end in the Central Directory,
the zipfile module can inspect, list, or selectively unpack
individual files without decompressing the entire archive.
How zipfile Compresses
Data
The zipfile module itself is written in pure Python,
located in the standard library as zipfile.py. However,
heavy mathematical operations for compression are delegated to built-in
C-extensions that come pre-packaged with Python.
Compression Modes
When writing files to an archive, zipfile supports
several compression algorithms specified via the
compression argument:
zipfile.ZIP_STORED(Default): No compression. Files are copied directly into the archive with metadata headers.zipfile.ZIP_DEFLATED: The standard ZIP compression method. It utilizes Python's built-inzlibmodule to apply the DEFLATE algorithm (a combination of LZ77 and Huffman coding).zipfile.ZIP_BZIP2: Uses thebz2module for higher compression ratios at the cost of speed and memory.zipfile.ZIP_LZMA: Uses thelzmamodule (often associated with.xzand7zformats) for maximum compression efficiency.
Writing an Archive
When you add a file using .write() or
.writestr(), zipfile executes the following
sequence:
- Header Creation: It generates a local file header with a temporary CRC-32 checksum and size values.
- Streaming and Compressing: It reads the source file
in chunks (typically 64 KB or custom buffer sizes) and feeds them into
the chosen compressor object (such as
zlib.compressobj). - Writing Payload: The compressed stream is written to the archive target.
- Directory Staging: The file's metadata, byte position, and final CRC-32 checksum are appended to an in-memory Central Directory list.
- Finalization: When the
ZipFileobject closes, it writes the complete Central Directory and the EOCD record to the end of the file.
import zipfile
files_to_pack = ['document.txt', 'data.csv']
with zipfile.ZipFile('archive.zip', mode='w', compression=zipfile.ZIP_DEFLATED) as archive:
for file in files_to_pack:
archive.write(file)To create files directly from memory without saving them to the disk
first, use writestr():
with zipfile.ZipFile('archive.zip', mode='a', compression=zipfile.ZIP_DEFLATED) as archive:
archive.writestr('virtual_file.txt', 'This data was generated in memory.')How zipfile Extracts
Data
Extraction is an inverse process optimized by the Central Directory index.
Reading and Validation
When zipfile.ZipFile opens an existing archive in read
mode ('r'):
- It seeks to the end of the file to find the EOCD record.
- It parses the location of the Central Directory and reads the
complete index into memory as
ZipInfoobjects. - Because the directory is already mapped, operations like
archive.namelist()or inspecting timestamps take minimal time and zero decompression overhead.
Decompressing Data
When extracting an item via .extract() or reading it via
.open():
zipfilechecks theZipInforecord for the file's exact byte offset in the archive.- The file pointer seeks directly to the local data stream.
- It passes the raw compressed bytes through a decompressor object
(such as
zlib.decompressobj). - As chunks decompress, the module calculates the running CRC-32 checksum.
- If the calculated checksum does not match the header's recorded
checksum, a
zipfile.BadZipFileexception is raised, ensuring file integrity.
import zipfile
# Extracting all files to a specific directory
with zipfile.ZipFile('archive.zip', mode='r') as archive:
archive.extractall(path='extracted_files/')
# Extracting or reading a single file into memory
with zipfile.ZipFile('archive.zip', mode='r') as archive:
with archive.open('document.txt') as file:
content = file.read().decode('utf-8')
print(content)Security Considerations: Zip Slip
Historically, malicious ZIP files could contain relative paths (e.g.,
../../etc/passwd) designed to write files outside the
target directory during extraction—a vulnerability known as "Zip
Slip."
Modern versions of Python mitigate this:
zipfile.extract()andzipfile.extractall()strip leading slashes and drive letters automatically.- In Python 3.12+, extraction filters (such as
filter='data') can be explicitly applied to sanitize file paths and prevent dangerous symlink traversal.