zipimport — Cargar código Python desde archivos ZIP

Propósito:Importa módulos Python guardados como miembros de archivos ZIP.

El módulo zipimport implementa la clase zipimporter, que se puede utilizar para buscar y cargar módulos Python dentro de archivos ZIP. El zipimporter admite la interfaz de programación de ganchos de importación especificada en PEP 302; así es como funcionan los eggs de Python.

Por lo general, no es necesario usar el módulo zipimport directamente, ya que es posible importar directamente desde un archivo ZIP siempre que ese archivo aparezca en sys.path. Sin embargo, es instructivo estudiar cómo se puede utilizar la interfaz de programación del importador, conocer las funciones disponibles y comprender cómo funciona la importación de módulos. Saber cómo funciona el importador ZIP también ayudará a solucionar problemas que pueden surgir al distribuir aplicaciones empaquetadas como archivos ZIP creados con zipfile.PyZipFile.

Ejemplo

Estos ejemplos reutilizan parte del código de la discusión de zipfile para crear un archivo ZIP de ejemplo que contenga algunos módulos de Python.

zipimport_make_example.py
import sys
import zipfile

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

Ejecuta zipimport_make_example.py antes de cualquiera de los ejemplos para crear un archivo ZIP que contenga todos los módulos en el directorio de ejemplo, junto con algunos datos de prueba necesarios para los ejemplos en esta sección.

$ python3 zipimport_make_example.py

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

Encontrar un módulo

Dado el nombre completo de un módulo, find_module() intentará ubicar ese módulo dentro del archivo ZIP.

zipimport_find_module.py
import zipimport

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

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

Si se encuentra el módulo, se devuelve la instancia zipimporter. De lo contrario, se devuelve None.

$ python3 zipimport_find_module.py

zipimport_find_module : <zipimporter object
"zipimport_example.zip">
not_there : None

Acceder al código

El método get_code() carga el objeto de código para un módulo desde el archivo.

zipimport_get_code.py
import zipimport

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

El objeto de código no es lo mismo que un objeto module, pero se usa para crear uno.

$ python3 zipimport_get_code.py

<code object <module> at 0x1012b4ae0, file
"./zipimport_get_code.py", line 6>

Para cargar el código como un módulo utilizable, usa load_module() en su lugar.

zipimport_load_module.py
import zipimport

importer = zipimport.zipimporter('zipimport_example.zip')
module = importer.load_module('zipimport_get_code')
print('Name   :', module.__name__)
print('Loader :', module.__loader__)
print('Code   :', module.code)

El resultado es un objeto de módulo configurado como si el código se hubiera cargado en una importación normal.

$ python3 zipimport_load_module.py

<code object <module> at 0x1007b4c00, file
"./zipimport_get_code.py", line 6>
Name   : zipimport_get_code
Loader : <zipimporter object "zipimport_example.zip">
Code   : <code object <module> at 0x1007b4c00, file
"./zipimport_get_code.py", line 6>

Código fuente

Al igual que con el módulo inspect, es posible recuperar el código fuente de un módulo del archivo ZIP, si el archivo incluye el código fuente. En el caso del ejemplo, solo se agrega zipimport_get_source.py a zipimport_example.zip (el resto de los módulos solo se agregan como archivos .pyc).

zipimport_get_source.py
import zipimport

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

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

Si el código fuente de un módulo no está disponible, get_source() devuelve None.

$ python3 zipimport_get_source.py

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

================================================================
zipimport_get_source
================================================================
#!/usr/bin/env python3
#
# Copyright 2007 Doug Hellmann.
#
"""Retrieving the source code for a module within a zip archive.
"""

#end_pymotw_header
import zipimport

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

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

Paquetes

Para determinar si un nombre se refiere a un paquete en lugar de un módulo normal, usa is_package().

zipimport_is_package.py
import zipimport

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

En este caso, zipimport_is_package provino de un módulo y el example_package es un paquete.

$ python3 zipimport_is_package.py

zipimport_is_package False
example_package True

Datos

Hay momentos en que los módulos o paquetes fuente deben distribuirse con datos que no son de código. Imágenes, archivos de configuración, datos predeterminados y accesorios de prueba son solo algunos ejemplos de esto. Con frecuencia, los atributos del módulo __path__ o __file__ se utilizan para encontrar estos archivos de datos en relación con el lugar donde está instalado el código.

Por ejemplo, con un módulo «normal», la ruta del sistema de archivos se puede construir a partir del atributo __file__ del paquete importado de esta manera:

zipimport_get_data_nozip.py
import os
import example_package

# Find the directory containing the imported
# package and build the data filename from it.
pkg_dir = os.path.dirname(example_package.__file__)
data_filename = os.path.join(pkg_dir, 'README.txt')

# Read the file and show its contents.
print(data_filename, ':')
print(open(data_filename, 'r').read())

El resultado dependerá de dónde se encuentre el código de muestra en el sistema de archivos.

$ python3 zipimport_get_data_nozip.py

.../example_package/README.txt :
This file represents sample data which could be embedded in the
ZIP archive.  You could include a configuration file, images, or
any other sort of noncode data.

Si el example_package se importa desde el archivo ZIP en lugar del sistema de archivos, usar __file__ no funciona.

zipimport_get_data_zip.py
import sys
sys.path.insert(0, 'zipimport_example.zip')

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

El __file__ del paquete se refiere al archivo ZIP y no a un directorio, por lo que construir la ruta al archivo README.txt da un valor incorrecto.

$ python3 zipimport_get_data_zip.py

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

Una forma más confiable de recuperar el archivo es usar el método get_data(). Se puede acceder a la instancia de zipimporter que cargó el módulo a través del atributo __loader__ del módulo importado:

zipimport_get_data.py
import sys
sys.path.insert(0, 'zipimport_example.zip')

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

pkgutil.get_data() usa esta interfaz para acceder a los datos desde un paquete. El valor devuelto es una cadena de bytes, que debe decodificarse en una cadena unicode antes de imprimir.

$ python3 zipimport_get_data.py

zipimport_example.zip/example_package/__init__.pyc
This file represents sample data which could be embedded in the
ZIP archive.  You could include a configuration file, images, or
any other sort of noncode data.

El __loader__ no está configurado para módulos no importados a través de zipimport.

Ver también