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.