zipimport - Carica Codice Python da Archivi ZIP

Scopo: Importa moduli Python salvati come membri di archivi ZIP.

Il modulo zipimport implementa la classe zipimporter, che può essere usata per trovare e caricare moduli Python all'interno di archivi in formato .ZIP. zipimporter supporta l'API di agganci di importazione specificati nel PEP 302; è il modo nel quale funzionano i Python Eggs. Un Python Egg è un formato di distribuzione di pacchetti python - n.d.t..

In genere non è necessario usare il modulo zipimport direttamente, visto che è possibile importare direttamente da un archivio ZIP fintanto che lo stesso sia nel percorso indirizzato da sys.path. Tuttavia è istruttivo studiare come l'API di importazione possa essere usata, per imparare le funzionalità disponibili e comprendere come funziona l'importazione dei moduli. Conoscere come funziona l'importatore da ZIP aiuta anche a indirizzare problemi di debug che potrebbero sorgere quando si distribuiscono applicazioni basate su archivi ZIP creati con zipfile.PyZipFile.

Esempio

Questi esempi riutilizzano parte del codice usato per l'articolo su zipfile per creare un archivio ZIP di esempio che contiene alcuni moduli Python.

# zipimport_make_example.py

import sys
import zipfile

if __name__ == '__main__':
    zf = zipfile.PyZipFile('esempio_zipimport.zip', mode='w')
    try:
        zf.writepy('.')
        zf.write('zipimport_get_source.py')
        zf.write('pacchetto_esempio/README.txt')
    finally:
        zf.close()
    for name in zf.namelist():
        print(name)

Per eseguire tutti gli esempi in questo articolo occorre:

  • scaricare gli esempi in una directory dedicata
  • creare un file __init__.py
  • creare una sottodirectory pacchetto_esempio
  • creare in pacchetto_esempio un file README.txt
  • creare in pacchetto_esempio il file __init__.py

Si esegua poi zipimport_make_example.py per creare un archivio ZIP che contiene tutti i moduli nella directory di esempio, assieme ad alcuni dati di test necessari per gli esempi di questa sezione.

$ python3 zipimport_make_example.py

__init__.pyc
zipimport_get_data_zip.pyc
zipimport_get_data.pyc
zipimport_load_module.pyc
zipimport_is_package.pyc
zipimport_find_module.pyc
pacchetto_esempio/__init__.pyc
zipimport_make_example.pyc
zipimport_get_code.pyc
zipimport_get_source.pyc
zipimport_get_data_nozip.pyc
zipimport_get_source.py
pacchetto_esempio/README.txt

Trovare un Modulo

Dato il nome completo di un modulo, find_module() cercherà di localizzarlo all'interno dell'archivio ZIP.

# zipimport_find_module.py

import zipimport

importer = zipimport.zipimporter('esempio_zipimport.zip')

for module_name in ['zipimport_find_module', 'non_qui']:
    print(module_name, ':', importer.find_module(module_name))

Se il modulo viene trovato, viene ritornata l'istanza di zipimport. Altrimenti viene restituito None.

$ python3 zipimport_find_module.py

zipimport_find_module : <zipimporter object "esempio_zipimport.zip">
non_qui : None

Accedere al Codice

Il metodo get_code() carica l'oggetto codice per un modulo dall'archivio.

# zipimport_get_code.py

import zipimport

importer = zipimport.zipimporter('esempio_zipimport.zip')
code = importer.get_code('zipimport_get_code')
print(code)

L'oggetto codice non è lo stesso dell'oggetto module, ma viene usato per crearne uno.

$ python3 zipimport_get_code.py

<code object <module> at 0x7f1c238b31e0, file "./zipimport_get_code.py", line 3>

Per caricare il codice come modulo utilizzabile si usi viceversa load_module().

# zipimport_load_module.py

import zipimport

importer = zipimport.zipimporter('esempio_zipimport.zip')
module = importer.load_module('zipimport_get_code')
print('Nome       :', module.__name__)
print('Caricatore :', module.__loader__)
print('Codice     :', module.code)

Il risultato è un oggetto module configurato come se il codice fosse stato caricato da una normale importazione.

$ python3 zipimport_load_module.py

<code object <module> at 0x7f7c703bae40, file "./zipimport_get_code.py", line 3>
Nome       : zipimport_get_code
Caricatore : <zipimporter object "esempio_zipimport.zip">
Codice     : <code object <module> at 0x7f7c703bae40, file "./zipimport_get_code.py", line 3>

Sorgente

Così come per il modulo inspect, è possibile recuperare il codice sorgente per un modulo da un archivio ZIP, se l'archivio include il sorgente. Nel caso dell'esempio, solo zipimport_get_source.py viene aggiunto ad esempio_zipimport.zip (il resto dei moduli sono solo aggiunti come file .pyc).

# zipimport_get_source.py

import zipimport

modules = [
    'zipimport_get_code',
    'zipimport_get_source',
]

importer = zipimport.zipimporter('esempio_zipimport.zip')
for module_name in modules:
    source = importer.get_source(module_name)
    print('=' * 80)
    print(module_name)
    print('=' * 80)
    print(source)
    print()

Se il sorgente per un modulo non è disponibile, get_source() ritorna None.

$ python3 zipimport_get_source.py

================================================================================
zipimport_get_code
================================================================================
None

================================================================================
zipimport_get_source
================================================================================
# zipimport_get_source.py

import zipimport

modules = [
    'zipimport_get_code',
    'zipimport_get_source',
]

importer = zipimport.zipimporter('esempio_zipimport.zip')
for module_name in modules:
    source = importer.get_source(module_name)
    print('=' * 80)
    print(module_name)
    print('=' * 80)
    print(source)
    print()

Pacchetti

Per determinare se un nome si riferisce a un pacchetto invece che a un normale modulo, si usi is_package().

# zipimport_is_package.py

import zipimport

importer = zipimport.zipimporter('esempio_zipimport.zip')
for name in ['zipimport_is_package', 'pacchetto_esempio']:
    print(name, importer.is_package(name))

In questo caso zipimport.is_package proviene da un modulo ed esempio_pacchetto è un pacchetto.

$ python3 zipimport_is_package.py

zipimport_is_package False
pacchetto_esempio True

Dati

Ci sono volte nelle quali moduli o pacchetti devono essere distribuiti con dati diversi da codice: immagini, file di configurazione, dati predefiniti, impianti di test sono solo alcuni esempi. Frequentemente gli attributi di modulo __path__ o __file__ sono usati per trovare questi file dati in relazione a dove il codice viene installato.

Ad esempio con un modulo "normale", il percorso di sistema del file può essere costruito con l'attributo __file__ del pacchetto importato così:

# zipimport_get_data_nozip.py

import os
import pacchetto_esempio

# Trova la directory contenente il pacchetto
# importato e da lì costruisce il nome per il file di dati
pkg_dir = os.path.dirname(pacchetto_esempio.__file__)
data_filename = os.path.join(pkg_dir, 'README.txt')

# Legge il file e mostra il suo contenuto.
print(data_filename, ':')
print(open(data_filename, 'r').read())

Il risultato dipenderà da dove si trova il codice di esempio sul file system.

$ python3 zipimport_get_data_nozip.py

.../pacchetto_esempio/README.txt :
Questo file rappresenta dati di esempio che potrebbero essere inseriti
nell'archivio ZIP. Si potrebbe includere un file di configurazione,
immagini o qualsiasi altro tipo di dato.

Se esempio_pacchetto viene importato dall'archivio ZIP invece che dal file system, non funzionerà l'uso di __file__.

# zipimport_get_data_zip.py

import sys
sys.path.insert(0, 'esempio_zipimport.zip')

import os
import pacchetto_esempio
print(pacchetto_esempio.__file__)
data_filename = os.path.join(
    os.path.dirname(pacchetto_esempio.__file__),
    'README.txt',
)
print(data_filename, ':')
print(open(data_filename, 'rt').read())

Il __file__ del pacchetto si riferisce all'archivio ZIP, e non a una directory, quindi la costruzione del percorso per README.txt fornirà un valore errato.

$ python3 zipimport_get_data_zip.py

esempio_zipimport.zip/pacchetto_esempio/__init__.pyc
esempio_zipimport.zip/pacchetto_esempio/README.txt :
Traceback (most recent call last):
  File "zipimport_get_data_zip.py", line 14, in <module>
    print(open(data_filename, 'rt').read())
NotADirectoryError: [Errno 20] Not a directory: 'esempio_zipimport.zip/pacchetto_esempio/README.txt'

Un modo più affidabile di recuperare il file è l'uso del metodo get_data(). L'istanza di zipimporter che ha caricato il modulo può essere indirizzata attraverso l'attributo __loader__ del modulo importato.

# zipimport_get_data.py

import sys
sys.path.insert(0, 'esempio_zipimport.zip')

import os
import pacchetto_esempio
print(pacchetto_esempio.__file__)
data = pacchetto_esempio.__loader__.get_data(
    'pacchetto_esempio/README.txt')
print(data.decode('utf-8'))

pkgutil.get_data() usa questa interfaccia per accedere a dati dall'interno di un pacchetto. Il valore restituito è una stringa di byte, che deve essere decodificata verso una stringa unicode prima della stampa.

$ python3 zipimport_get_data.py

esempio_zipimport.zip/pacchetto_esempio/__init__.pyc
Questo file rappresenta dati di esempio che potrebbero essere inseriti
nell'archivio ZIP. Si potrebbe includere un file di configurazione,
immagini o qualsiasi altro tipo di dato.

__loader__ non è impostato per moduli non importati via zipimport.

Vedere anche:

zipimport
La documentazione della libreria standard per questo modulo.
Note di portabilità da Python 2 a 3 per zipimport
pkgutil
Fornisce una interfaccia più generica a get_data().
zipfile
Legge e scrive file di archivio ZIP.
PEP 302
Nuovi agganci di importazione.