Python's zipfile module covers most ZIP work you would otherwise do with external tools: checking file types, listing members, reading metadata, extracting, creating archives, and appending entries. The examples below use the Python 2 syntax of the original snippets, but the module behavior is the same for modern Python with minor print changes.
Checking whether a file is a ZIP archive
zipfile.is_zipfile() gives a quick yes or no without opening the archive for a full read.
#!/bin/env python
# -*- coding:utf-8 -*-
import zipfile
for filename in [ 'test.txt', 'test.zip',
'test.jpg', 'test.py' ]:
print '%20s %s' % (filename, zipfile.is_zipfile(filename))
Output:
[root@the5 py]# ls
test.jpg test.py test.txt test.zip
[root@the5 py]# ./test.py
test.txt False
test.zip True
test.jpg False
test.py False
[root@the5 py]#
Only test.zip is recognized as a ZIP file; the plain text, image, and Python source files are not.
Listing the members of an archive
namelist() returns the names stored in the archive.
#!/bin/env python
# -*- coding:utf-8 -*-
import zipfile
zf = zipfile.ZipFile('test.zip', 'r')
print zf.namelist()
Output:
[root@the5 py]# ./zipfile_namelist.py
['test.txt']
[root@the5 py]# unzip -t test.zip
Archive: test.zip
testing: test.txt OK
No errors detected in compressed data of test.zip.
The list matches what unzip -t reports. For more detail than just names, iterate over infolist().
Inspecting archive metadata
Each entry in infolist() exposes the filename, comment, modification time, creator system, ZIP version, and both compressed and uncompressed sizes.
import datetime
import zipfile
def print_info(archive_name):
zf = zipfile.ZipFile(archive_name)
for info in zf.infolist():
print info.filename
print '\tComment:\t', info.comment
print '\tModified:\t', datetime.datetime(*info.date_time)
print '\tSystem:\t\t', info.create_system, '(0 = Windows, 3 = Unix)'
print '\tZIP version:\t', info.create_version
print '\tCompressed:\t', info.compress_size, 'bytes'
print '\tUncompressed:\t', info.file_size, 'bytes'
print
if __name__ == '__main__':
print_info('IMG_201509.zip')
Output:
[root@the5 py]# ./test3.py
...
IMG_20150929_203432.jpg
Comment:
Modified: 2015-09-29 20:34:32
System: 0 (0 = Windows, 3 = Unix)
ZIP version: 63
Compressed: 5840550 bytes
Uncompressed: 5956792 bytes
IMG_20150930_091243.jpg
Comment:
Modified: 2015-09-30 09:12:42
System: 0 (0 = Windows, 3 = Unix)
ZIP version: 63
Compressed: 2818819 bytes
Uncompressed: 2932052 bytes
...
This prints the details for every file in the archive.
Getting the size of a specific member
getinfo() raises KeyError when the name is not present, so it is easy to report a missing entry separately from a successful lookup.
import zipfile
zf = zipfile.ZipFile('example.zip')
for filename in [ 'README.txt', 'notthere.txt' ]:
try:
info = zf.getinfo(filename)
except KeyError:
print 'ERROR: Did not find %s in zip file' % filename
else:
print '%s is %d bytes' % (info.filename, info.file_size)
Output:
$ python zipfile_getinfo.py
README.txt is 75 bytes
ERROR: Did not find notthere.txt in zip file
Extracting a member
To pull one file out of an archive, read its bytes with read() and write them to a local file.
#!/bin/env python
# -*- coding:utf-8 -*-
import zipfile
zf = zipfile.ZipFile('vivo-201509.zip')
for filename in [ 'IMG_20150929_203432.jpg' ]:
try:
data = zf.read(filename)
except KeyError:
print 'ERROR: Did not find %s in zip file' % filename
else:
print filename
with open('img.jpg', 'wb') as f:
f.write(data)
Creating an archive
Writing with mode='w' creates a new archive. By default, members are stored without compression; the archive is a container rather than a space-saving compressed file.
from zipfile_infolist import print_info
import zipfile
print 'creating archive'
zf = zipfile.ZipFile('zipfile_write.zip', mode='w')
try:
print 'adding README.txt'
zf.write('README.txt')
finally:
print 'closing'
zf.close()
print
print_info('zipfile_write.zip')
To actually compress entries, use ZIP_DEFLATED, which depends on the zlib module. The code below falls back to ZIP_STORED if zlib is unavailable.
from zipfile_infolist import print_info
import zipfile
try:
import zlib
compression = zipfile.ZIP_DEFLATED
except:
compression = zipfile.ZIP_STORED
modes = { zipfile.ZIP_DEFLATED: 'deflated',
zipfile.ZIP_STORED: 'stored',
}
print 'creating archive'
zf = zipfile.ZipFile('zipfile_write_compression.zip', mode='w')
try:
print 'adding README.txt with compression mode', modes[compression]
zf.write('README.txt', compress_type=compression)
finally:
print 'closing'
zf.close()
print
print_info('zipfile_write_compression.zip')
Output:
$ python zipfile_write_compression.py
creating archive
adding README.txt with compression mode deflated
closing
README.txt
Comment:
Modified: 2007-12-16 10:08:50
System: 3 (0 = Windows, 3 = Unix)
ZIP version: 20
Compressed: 63 bytes
Uncompressed: 75 bytes
Renaming a file inside the archive
The arcname argument sets the member name independently of the source path.
from zipfile_infolist import print_info
import zipfile
zf = zipfile.ZipFile('zipfile_write_arcname.zip', mode='w')
try:
zf.write('README.txt', arcname='NOT_README.txt')
finally:
zf.close()
print_info('zipfile_write_arcname.zip')
Output:
$ python zipfile_write_arcname.py
NOT_README.txt
Comment:
Modified: 2007-12-16 10:08:50
System: 3 (0 = Windows, 3 = Unix)
ZIP version: 20
Compressed: 75 bytes
Uncompressed: 75 bytes
Writing string data directly
writestr() adds bytes or text without requiring a file on disk first. In this example the string is written under the name from_string.txt, and the archive is opened again to read it back.
from zipfile_infolist import print_info
import zipfile
msg = 'This data did not exist in a file before being added to the ZIP file'
zf = zipfile.ZipFile('zipfile_writestr.zip',
mode='w',
compression=zipfile.ZIP_DEFLATED,
)
try:
zf.writestr('from_string.txt', msg)
finally:
zf.close()
print_info('zipfile_writestr.zip')
zf = zipfile.ZipFile('zipfile_writestr.zip', 'r')
print zf.read('from_string.txt')
Output:
$ python zipfile_writestr.py
from_string.txt
Comment:
Modified: 2007-12-16 11:38:14
System: 3 (0 = Windows, 3 = Unix)
ZIP version: 20
Compressed: 62 bytes
Uncompressed: 68 bytes
Appending entries to an existing archive
Opening with mode='a' adds entries to an archive that already exists.
from zipfile_infolist import print_info
import zipfile
print 'creating archive'
zf = zipfile.ZipFile('zipfile_append.zip', mode='w')
try:
zf.write('README.txt')
finally:
zf.close()
print
print_info('zipfile_append.zip')
print 'appending to the archive'
zf = zipfile.ZipFile('zipfile_append.zip', mode='a')
try:
zf.write('README.txt', arcname='README2.txt')
finally:
zf.close()
print
print_info('zipfile_append.zip')
A faster option with czipfile
If archive reading is a bottleneck, czipfile can be used as a drop-in replacement. Install the Python development headers first, then the package.
dnf install python-devel
pip install czipfile
The change is mostly mechanical: use czipfile where the code used zipfile. On a roughly 354 MB photo archive, the difference in extraction time was noticeable in the benchmark below.
[root@the5 py]# du -sh ./vivo-201509.zip
354M ./vivo-201509.zip
[root@the5 py]# time ./test-zipfile.py
real 0m2.203s
user 0m1.464s
sys 0m0.542s
[root@the5 py]# time ./test-czipfile.py
real 0m1.630s
user 0m0.989s
sys 0m0.482s
The same archive dropped from about 2.2 seconds to 1.6 seconds in this test, with lower user and system time as well. For larger archives or repeated operations, that saving can justify the extra dependency.