Módulos e Importaciones

La mayoría de los programas de Python terminan como una combinación de varios módulos con una aplicación principal que los importa. Ya sea el uso de las características de la biblioteca estándar o la organización de código personalizado en archivos separados para facilitar el mantenimiento, la comprensión y la administración de las dependencias de un programa es un aspecto importante del desarrollo. sys incluye información sobre los módulos disponibles para una aplicación, ya sea como integrados o después de ser importados. También define ganchos para anular el comportamiento de importación estándar para casos especiales.

Módulos importados

sys.modules es un diccionario que asigna los nombres de los módulos importados al objeto del módulo que contiene el código.

sys_modules.py
import sys
import textwrap

names = sorted(sys.modules.keys())
name_text = ', '.join(names)

print(textwrap.fill(name_text, width=64))

El contenido de `` sys.modules`` cambia a medida que se importan nuevos módulos.

$ python3 sys_modules.py

__main__, _bootlocale, _codecs, _collections, _collections_abc,
_frozen_importlib, _frozen_importlib_external, _functools,
_heapq, _imp, _io, _locale, _operator, _signal, _sre, _stat,
_thread, _warnings, _weakref, _weakrefset, abc, builtins,
codecs, collections, collections.abc, contextlib, copyreg,
encodings, encodings.aliases, encodings.latin_1,
encodings.utf_8, enum, errno, functools, genericpath, heapq,
importlib, importlib._bootstrap, importlib._bootstrap_external,
importlib.abc, importlib.machinery, importlib.util, io,
itertools, keyword, marshal, operator, os, os.path, posix,
posixpath, re, reprlib, site, sphinxcontrib, sre_compile,
sre_constants, sre_parse, stat, sys, textwrap, types, warnings,
weakref, zipimport

Módulos incorporados

El intérprete de Python se puede compilar con algunos módulos C integrados, por lo que no es necesario distribuirlos como bibliotecas compartidas separadas. Estos módulos no aparecen en la lista de módulos importados administrados en sys.modules porque técnicamente no se importaron. La única forma de encontrar los módulos integrados disponibles es a través de sys.builtin_module_names.

sys_builtins.py
import sys
import textwrap

name_text = ', '.join(sorted(sys.builtin_module_names))

print(textwrap.fill(name_text, width=64))

El resultado de esta secuencia de comandos variará, especialmente si se ejecuta con una versión personalizada del intérprete. Este resultado se creó utilizando una copia del intérprete instalado desde el instalador estándar de python.org para OS X.

$ python3 sys_builtins.py

_ast, _codecs, _collections, _functools, _imp, _io, _locale,
_operator, _signal, _sre, _stat, _string, _symtable, _thread,
_tracemalloc, _warnings, _weakref, atexit, builtins, errno,
faulthandler, gc, itertools, marshal, posix, pwd, sys, time,
xxsubtype, zipimport

Ver también

Ruta de importación

La ruta de búsqueda de módulos se gestiona como una lista de Python guardada en sys.path. El contenido predeterminado de la ruta incluye el directorio de la secuencia de comandos utilizada para iniciar la aplicación y el directorio de trabajo actual.

sys_path_show.py
import sys

for d in sys.path:
    print(d)

El primer directorio en la ruta de búsqueda es el hogar de la secuencia de comandos de muestra en sí. Esta es seguida por una serie de rutas específicas de la plataforma donde se pueden instalar módulos de extensión compilados (escritos en C), y luego el directorio global de site-packages aparece en último lugar.

$ python3 sys_path_show.py

/Users/dhellmann/Documents/PyMOTW/pymotw-3/source/sys
.../python35.zip
.../lib/python3.5
.../lib/python3.5/plat-darwin
.../python3.5/lib-dynload
.../lib/python3.5/site-packages

La lista de ruta de búsqueda de importación se puede modificar antes de iniciar el intérprete configurando la variable de shell PYTHONPATH en una lista de directorios separados por dos puntos.

$ PYTHONPATH=/my/private/site-packages:/my/shared/site-packages \
> python3 sys_path_show.py

/Users/dhellmann/Documents/PyMOTW/pymotw-3/source/sys
/my/private/site-packages
/my/shared/site-packages
.../python35.zip
.../lib/python3.5
.../lib/python3.5/plat-darwin
.../python3.5/lib-dynload
.../lib/python3.5/site-packages

Un programa también puede modificar su ruta agregando elementos a sys.path directamente.

sys_path_modify.py
import imp
import os
import sys

base_dir = os.path.dirname(__file__) or '.'
print('Base directory:', base_dir)

# Insert the package_dir_a directory at the front of the path.
package_dir_a = os.path.join(base_dir, 'package_dir_a')
sys.path.insert(0, package_dir_a)

# Import the example module
import example
print('Imported example from:', example.__file__)
print('  ', example.DATA)

# Make package_dir_b the first directory in the search path
package_dir_b = os.path.join(base_dir, 'package_dir_b')
sys.path.insert(0, package_dir_b)

# Reload the module to get the other version
imp.reload(example)
print('Reloaded example from:', example.__file__)
print('  ', example.DATA)

La recarga de un módulo importado vuelve a importar el archivo y utiliza el mismo objeto module para guardar los resultados. Cambiar la ruta entre la importación inicial y la llamada a reload() significa que se puede cargar un módulo diferente la segunda vez.

$ python3 sys_path_modify.py

Base directory: .
Imported example from: ./package_dir_a/example.py
   This is example A
Reloaded example from: ./package_dir_b/example.py
   This is example B

Importadores personalizados

La modificación de la ruta de búsqueda le permite al programador controlar cómo se encuentran los módulos Python estándar. Pero, ¿qué sucede si un programa necesita importar código desde otro lugar que no sean los archivos habituales .py o .pyc en el sistema de archivos? PEP 302 resuelve este problema al introducir la idea de * ganchos de importación*, que puede atrapar un intento de encontrar un módulo en la ruta de búsqueda y tomar medidas alternativas para cargar el código desde otro lugar o aplicarle un pre-procesamiento.

Los importadores personalizados se implementan en dos fases separadas. El buscador es responsable de localizar un módulo y de proporcionar un cargador para administrar la importación real. Los buscadores de módulos personalizados se agregan agregando una fábrica a la lista sys.path_hooks. En la importación, cada parte de la ruta se entrega a un buscador hasta que uno reclame soporte (al no generar ImportError). Ese buscador es responsable de buscar el almacenamiento de datos representado por su entrada de ruta para módulos con nombre.

sys_path_hooks_noisy.py
import sys


class NoisyImportFinder:

    PATH_TRIGGER = 'NoisyImportFinder_PATH_TRIGGER'

    def __init__(self, path_entry):
        print('Checking {}:'.format(path_entry), end=' ')
        if path_entry != self.PATH_TRIGGER:
            print('wrong finder')
            raise ImportError()
        else:
            print('works')
        return

    def find_module(self, fullname, path=None):
        print('Looking for {!r}'.format(fullname))
        return None


sys.path_hooks.append(NoisyImportFinder)

for hook in sys.path_hooks:
    print('Path hook: {}'.format(hook))

sys.path.insert(0, NoisyImportFinder.PATH_TRIGGER)

try:
    print('importing target_module')
    import target_module
except Exception as e:
    print('Import failed:', e)

Este ejemplo ilustra cómo se crean instancias y consultas de los buscadores. El NoisyImportFinder genera ImportError cuando se instancia con una entrada de ruta que no coincide con su valor de activación especial, que obviamente no es una ruta real en el sistema de archivos. Esta prueba evita que el NoisyImportFinder rompa las importaciones de módulos reales.

$ python3 sys_path_hooks_noisy.py

Path hook: <class 'zipimport.zipimporter'>
Path hook: <function
FileFinder.path_hook.<locals>.path_hook_for_FileFinder at
0x101afb6a8>
Path hook: <class '__main__.NoisyImportFinder'>
importing target_module
Checking NoisyImportFinder_PATH_TRIGGER: works
Looking for 'target_module'
Import failed: No module named 'target_module'

Importar desde un estante

Cuando el buscador localiza un módulo, es responsable de devolver un cargador capaz de importar ese módulo. Este ejemplo ilustra un importador personalizado que guarda el contenido de su módulo en una base de datos creada por shelve.

Primero, se usa una secuencia de comandos para llenar el estante con un paquete que contiene un submódulo y un subpaquete.

sys_shelve_importer_create.py
import shelve
import os

filename = '/tmp/pymotw_import_example.shelve'
if os.path.exists(filename + '.db'):
    os.unlink(filename + '.db')
with shelve.open(filename) as db:
    db['data:README'] = b"""
==============
package README
==============

This is the README for ``package``.
"""
    db['package.__init__'] = b"""
print('package imported')
message = 'This message is in package.__init__'
"""
    db['package.module1'] = b"""
print('package.module1 imported')
message = 'This message is in package.module1'
"""
    db['package.subpackage.__init__'] = b"""
print('package.subpackage imported')
message = 'This message is in package.subpackage.__init__'
"""
    db['package.subpackage.module2'] = b"""
print('package.subpackage.module2 imported')
message = 'This message is in package.subpackage.module2'
"""
    db['package.with_error'] = b"""
print('package.with_error being imported')
raise ValueError('raising exception to break import')
"""
    print('Created {} with:'.format(filename))
    for key in sorted(db.keys()):
        print('  ', key)

Una secuencia de comandos de empaquetado real leería el contenido del sistema de archivos, pero el uso de valores codificados es suficiente para un ejemplo simple como este.

$ python3 sys_shelve_importer_create.py

Created /tmp/pymotw_import_example.shelve with:
   data:README
   package.__init__
   package.module1
   package.subpackage.__init__
   package.subpackage.module2
   package.with_error

Una secuencia de comandos de empaquetado real leería el contenido del sistema de archivos, pero el uso de valores codificados es suficiente para un ejemplo simple como este.

sys_shelve_importer.py
import imp
import os
import shelve
import sys


def _mk_init_name(fullname):
    """Return the name of the __init__ module
    for a given package name.
    """
    if fullname.endswith('.__init__'):
        return fullname
    return fullname + '.__init__'


def _get_key_name(fullname, db):
    """Look in an open shelf for fullname or
    fullname.__init__, return the name found.
    """
    if fullname in db:
        return fullname
    init_name = _mk_init_name(fullname)
    if init_name in db:
        return init_name
    return None


class ShelveFinder:
    """Find modules collected in a shelve archive."""

    _maybe_recursing = False

    def __init__(self, path_entry):
        # Loading shelve causes an import recursive loop when it
        # imports dbm, and we know we are not going to load the
        # module # being imported, so when we seem to be
        # recursing just ignore the request so another finder
        # will be used.
        if ShelveFinder._maybe_recursing:
            raise ImportError
        try:
            # Test the path_entry to see if it is a valid shelf
            try:
                ShelveFinder._maybe_recursing = True
                with shelve.open(path_entry, 'r'):
                    pass
            finally:
                ShelveFinder._maybe_recursing = False
        except Exception as e:
            print('shelf could not import from {}: {}'.format(
                path_entry, e))
            raise
        else:
            print('shelf added to import path:', path_entry)
            self.path_entry = path_entry
        return

    def __str__(self):
        return '<{} for {!r}>'.format(self.__class__.__name__,
                                      self.path_entry)

    def find_module(self, fullname, path=None):
        path = path or self.path_entry
        print('\nlooking for {!r}\n  in {}'.format(
            fullname, path))
        with shelve.open(self.path_entry, 'r') as db:
            key_name = _get_key_name(fullname, db)
            if key_name:
                print('  found it as {}'.format(key_name))
                return ShelveLoader(path)
        print('  not found')
        return None


class ShelveLoader:
    """Load source for modules from shelve databases."""

    def __init__(self, path_entry):
        self.path_entry = path_entry
        return

    def _get_filename(self, fullname):
        # Make up a fake filename that starts with the path entry
        # so pkgutil.get_data() works correctly.
        return os.path.join(self.path_entry, fullname)

    def get_source(self, fullname):
        print('loading source for {!r} from shelf'.format(
            fullname))
        try:
            with shelve.open(self.path_entry, 'r') as db:
                key_name = _get_key_name(fullname, db)
                if key_name:
                    return db[key_name]
                raise ImportError(
                    'could not find source for {}'.format(
                        fullname)
                )
        except Exception as e:
            print('could not load source:', e)
            raise ImportError(str(e))

    def get_code(self, fullname):
        source = self.get_source(fullname)
        print('compiling code for {!r}'.format(fullname))
        return compile(source, self._get_filename(fullname),
                       'exec', dont_inherit=True)

    def get_data(self, path):
        print('looking for data\n  in {}\n  for {!r}'.format(
            self.path_entry, path))
        if not path.startswith(self.path_entry):
            raise IOError
        path = path[len(self.path_entry) + 1:]
        key_name = 'data:' + path
        try:
            with shelve.open(self.path_entry, 'r') as db:
                return db[key_name]
        except Exception:
            # Convert all errors to IOError
            raise IOError()

    def is_package(self, fullname):
        init_name = _mk_init_name(fullname)
        with shelve.open(self.path_entry, 'r') as db:
            return init_name in db

    def load_module(self, fullname):
        source = self.get_source(fullname)

        if fullname in sys.modules:
            print('reusing module from import of {!r}'.format(
                fullname))
            mod = sys.modules[fullname]
        else:
            print('creating a new module object for {!r}'.format(
                fullname))
            mod = sys.modules.setdefault(
                fullname,
                imp.new_module(fullname)
            )

        # Set a few properties required by PEP 302
        mod.__file__ = self._get_filename(fullname)
        mod.__name__ = fullname
        mod.__path__ = self.path_entry
        mod.__loader__ = self
        # PEP-366 specifies that package's set __package__ to
        # their name, and modules have it set to their parent
        # package (if any).
        if self.is_package(fullname):
            mod.__package__ = fullname
        else:
            mod.__package__ = '.'.join(fullname.split('.')[:-1])

        if self.is_package(fullname):
            print('adding path for package')
            # Set __path__ for packages
            # so we can find the sub-modules.
            mod.__path__ = [self.path_entry]
        else:
            print('imported as regular module')

        print('execing source...')
        exec(source, mod.__dict__)
        print('done')
        return mod

Ahora ShelveFinder y ShelveLoader se pueden usar para importar código desde un estante. Por ejemplo, importando el package recién creado:

sys_shelve_importer_package.py
import sys
import sys_shelve_importer


def show_module_details(module):
    print('  message    :', module.message)
    print('  __name__   :', module.__name__)
    print('  __package__:', module.__package__)
    print('  __file__   :', module.__file__)
    print('  __path__   :', module.__path__)
    print('  __loader__ :', module.__loader__)


filename = '/tmp/pymotw_import_example.shelve'
sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
sys.path.insert(0, filename)

print('Import of "package":')
import package

print()
print('Examine package details:')
show_module_details(package)

print()
print('Global settings:')
print('sys.modules entry:')
print(sys.modules['package'])

El estante se agrega a la ruta de importación la primera vez que se produce una importación después de que se modifica la ruta. El buscador reconoce el estante y devuelve un cargador, que se utiliza para todas las importaciones desde ese estante. La importación inicial a nivel de paquete crea un nuevo objeto de módulo y luego usa exec para ejecutar la fuente cargada desde el estante. Utiliza el nuevo módulo como espacio de nombres para que los nombres definidos en la fuente se conserven como atributos de nivel de módulo.

$ python3 sys_shelve_importer_package.py

Import of "package":
shelf added to import path: /tmp/pymotw_import_example.shelve

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
creating a new module object for 'package'
adding path for package
execing source...
package imported
done

Examine package details:
  message    : This message is in package.__init__
  __name__   : package
  __package__: package
  __file__   : /tmp/pymotw_import_example.shelve/package
  __path__   : ['/tmp/pymotw_import_example.shelve']
  __loader__ : <sys_shelve_importer.ShelveLoader object at
0x104589b70>

Global settings:
sys.modules entry:
<module 'package' (<sys_shelve_importer.ShelveLoader object at
0x104589b70>)>

Importación de paquetes personalizados

La carga de otros módulos y subpaquetes se realiza de la misma manera.

sys_shelve_importer_module.py
import sys
import sys_shelve_importer


def show_module_details(module):
    print('  message    :', module.message)
    print('  __name__   :', module.__name__)
    print('  __package__:', module.__package__)
    print('  __file__   :', module.__file__)
    print('  __path__   :', module.__path__)
    print('  __loader__ :', module.__loader__)


filename = '/tmp/pymotw_import_example.shelve'
sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
sys.path.insert(0, filename)

print('Import of "package.module1":')
import package.module1

print()
print('Examine package.module1 details:')
show_module_details(package.module1)

print()
print('Import of "package.subpackage.module2":')
import package.subpackage.module2

print()
print('Examine package.subpackage.module2 details:')
show_module_details(package.subpackage.module2)

El buscador recibe el nombre punteado completo del módulo para cargar, y devuelve un ShelveLoader configurado para cargar módulos desde la entrada de la ruta que apunta al archivo del estante. El nombre del módulo completo se pasa al método del cargador load_module(), que construye y devuelve una instancia de module.

$ python3 sys_shelve_importer_module.py

Import of "package.module1":
shelf added to import path: /tmp/pymotw_import_example.shelve

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
creating a new module object for 'package'
adding path for package
execing source...
package imported
done

looking for 'package.module1'
  in /tmp/pymotw_import_example.shelve
  found it as package.module1
loading source for 'package.module1' from shelf
creating a new module object for 'package.module1'
imported as regular module
execing source...
package.module1 imported
done

Examine package.module1 details:
  message    : This message is in package.module1
  __name__   : package.module1
  __package__: package
  __file__   : /tmp/pymotw_import_example.shelve/package.module1
  __path__   : /tmp/pymotw_import_example.shelve
  __loader__ : <sys_shelve_importer.ShelveLoader object at
0x10457dc18>

Import of "package.subpackage.module2":

looking for 'package.subpackage'
  in /tmp/pymotw_import_example.shelve
  found it as package.subpackage.__init__
loading source for 'package.subpackage' from shelf
creating a new module object for 'package.subpackage'
adding path for package
execing source...
package.subpackage imported
done

looking for 'package.subpackage.module2'
  in /tmp/pymotw_import_example.shelve
  found it as package.subpackage.module2
loading source for 'package.subpackage.module2' from shelf
creating a new module object for 'package.subpackage.module2'
imported as regular module
execing source...
package.subpackage.module2 imported
done

Examine package.subpackage.module2 details:
  message    : This message is in package.subpackage.module2
  __name__   : package.subpackage.module2
  __package__: package.subpackage
  __file__   :
/tmp/pymotw_import_example.shelve/package.subpackage.module2
  __path__   : /tmp/pymotw_import_example.shelve
  __loader__ : <sys_shelve_importer.ShelveLoader object at
0x1045b5080>

Recarga de módulos en un importador personalizado

La recarga de un módulo se maneja de manera ligeramente diferente. En lugar de crear un nuevo objeto de módulo, el objeto existente se reutiliza.

sys_shelve_importer_reload.py
import importlib
import sys
import sys_shelve_importer

filename = '/tmp/pymotw_import_example.shelve'
sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
sys.path.insert(0, filename)

print('First import of "package":')
import package

print()
print('Reloading "package":')
importlib.reload(package)

Al reutilizar el mismo objeto, las referencias existentes al módulo se conservan incluso si la recarga modifica las definiciones de clase o función.

$ python3 sys_shelve_importer_reload.py

First import of "package":
shelf added to import path: /tmp/pymotw_import_example.shelve

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
creating a new module object for 'package'
adding path for package
execing source...
package imported
done

Reloading "package":

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
reusing module from import of 'package'
adding path for package
execing source...
package imported
done

Manejo de errores de importación

Cuando un buscador no puede ubicar un módulo, el código de importación principal genera ImportError.

sys_shelve_importer_missing.py
import sys
import sys_shelve_importer

filename = '/tmp/pymotw_import_example.shelve'
sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
sys.path.insert(0, filename)

try:
    import package.module3
except ImportError as e:
    print('Failed to import:', e)

Se propagan otros errores durante la importación.

$ python3 sys_shelve_importer_missing.py

shelf added to import path: /tmp/pymotw_import_example.shelve

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
creating a new module object for 'package'
adding path for package
execing source...
package imported
done

looking for 'package.module3'
  in /tmp/pymotw_import_example.shelve
  not found
Failed to import: No module named 'package.module3'

Datos del paquete

Además de definir la interfaz de programación para cargar código Python ejecutable, PEP 302 define una interfaz de programación opcional para recuperar datos de paquetes destinados a distribuir archivos de datos, documentación y otros recursos sin código utilizados por un paquete. Al implementar get_data(), un cargador puede permitir que las aplicaciones de llamada admitan la recuperación de datos asociados con el paquete sin considerar cómo se instala realmente el paquete (especialmente sin suponer que el paquete se almacena como archivos en un sistema de archivos).

sys_shelve_importer_get_data.py
import sys
import sys_shelve_importer
import os
import pkgutil

filename = '/tmp/pymotw_import_example.shelve'
sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
sys.path.insert(0, filename)

import package

readme_path = os.path.join(package.__path__[0], 'README')

readme = pkgutil.get_data('package', 'README')
# Equivalent to:
#  readme = package.__loader__.get_data(readme_path)
print(readme.decode('utf-8'))

foo_path = os.path.join(package.__path__[0], 'foo')
try:
    foo = pkgutil.get_data('package', 'foo')
    # Equivalent to:
    #  foo = package.__loader__.get_data(foo_path)
except IOError as err:
    print('ERROR: Could not load "foo"', err)
else:
    print(foo)

get_data() toma una ruta basada en el módulo o paquete que posee los datos, y devuelve el contenido del recurso del «archivo» como una cadena de bytes, o genera IOError si el recurso no existe.

$ python3 sys_shelve_importer_get_data.py

shelf added to import path: /tmp/pymotw_import_example.shelve

looking for 'package'
  in /tmp/pymotw_import_example.shelve
  found it as package.__init__
loading source for 'package' from shelf
creating a new module object for 'package'
adding path for package
execing source...
package imported
done
looking for data
  in /tmp/pymotw_import_example.shelve
  for '/tmp/pymotw_import_example.shelve/README'

==============
package README
==============

This is the README for ``package``.

looking for data
  in /tmp/pymotw_import_example.shelve
  for '/tmp/pymotw_import_example.shelve/foo'
ERROR: Could not load "foo"

Ver también

  • pkgutil – Includes get_data() for retrieving data from a package.

Caché de importadores

Buscar en todos los ganchos cada vez que se importa un módulo puede resultar costoso. Para ahorrar tiempo, sys.path_importer_cache se mantiene como un mapeo entre una entrada de ruta y el cargador que puede usar el valor para encontrar módulos.

sys_path_importer_cache.py
import os
import sys

prefix = os.path.abspath(sys.prefix)

print('PATH:')
for name in sys.path:
    name = name.replace(prefix, '...')
    print(' ', name)

print()
print('IMPORTERS:')
for name, cache_value in sys.path_importer_cache.items():
    if '..' in name:
        name = os.path.abspath(name)
    name = name.replace(prefix, '...')
    print('  {}: {!r}'.format(name, cache_value))

Se usa un FileFinder para las ubicaciones de ruta que se encuentran en el sistema de archivos. Las ubicaciones en la ruta no admitidas por ningún buscador están asociadas con un None, ya que no se pueden usar para importar módulos. El resultado a continuación se ha truncado debido a restricciones de formato.

$ python3 sys_path_importer_cache.py

PATH:
  /Users/dhellmann/Documents/PyMOTW/Python3/pymotw-3/source/sys
  .../lib/python35.zip
  .../lib/python3.5
  .../lib/python3.5/plat-darwin
  .../lib/python3.5/lib-dynload
  .../lib/python3.5/site-packages

IMPORTERS:
  sys_path_importer_cache.py: None
  .../lib/python3.5/encodings: FileFinder(
  '.../lib/python3.5/encodings')
  .../lib/python3.5/lib-dynload: FileFinder(
  '.../lib/python3.5/lib-dynload')
  .../lib/python3.5/lib-dynload: FileFinder(
  '.../lib/python3.5/lib-dynload')
  .../lib/python3.5/site-packages: FileFinder(
  '.../lib/python3.5/site-packages')
  .../lib/python3.5: FileFinder(
  '.../lib/python3.5/')
  .../lib/python3.5/plat-darwin: FileFinder(
  '.../lib/python3.5/plat-darwin')
  .../lib/python3.5: FileFinder(
  '.../lib/python3.5')
  .../lib/python35.zip: None
  .../lib/python3.5/plat-darwin: FileFinder(
  '.../lib/python3.5/plat-darwin')

Meta-Ruta

El sys.meta_path amplía aún más las fuentes de importaciones potenciales al permitir que se busque un buscador antes de escanear el sys.path normal. La interfaz de programación para un buscador en la meta-ruta es la misma que para una ruta normal. La diferencia es que el meta buscador no está limitado a una sola entrada en sys.path: puede buscar en cualquier lugar.

sys_meta_path.py
import sys
import imp


class NoisyMetaImportFinder:

    def __init__(self, prefix):
        print('Creating NoisyMetaImportFinder for {}'.format(
            prefix))
        self.prefix = prefix
        return

    def find_module(self, fullname, path=None):
        print('looking for {!r} with path {!r}'.format(
            fullname, path))
        name_parts = fullname.split('.')
        if name_parts and name_parts[0] == self.prefix:
            print(' ... found prefix, returning loader')
            return NoisyMetaImportLoader(path)
        else:
            print(' ... not the right prefix, cannot load')
        return None


class NoisyMetaImportLoader:

    def __init__(self, path_entry):
        self.path_entry = path_entry
        return

    def load_module(self, fullname):
        print('loading {}'.format(fullname))
        if fullname in sys.modules:
            mod = sys.modules[fullname]
        else:
            mod = sys.modules.setdefault(
                fullname,
                imp.new_module(fullname))

        # Set a few properties required by PEP 302
        mod.__file__ = fullname
        mod.__name__ = fullname
        # always looks like a package
        mod.__path__ = ['path-entry-goes-here']
        mod.__loader__ = self
        mod.__package__ = '.'.join(fullname.split('.')[:-1])

        return mod


# Install the meta-path finder
sys.meta_path.append(NoisyMetaImportFinder('foo'))

# Import some modules that are "found" by the meta-path finder
print()
import foo

print()
import foo.bar

# Import a module that is not found
print()
try:
    import bar
except ImportError as e:
    pass

Cada buscador en la meta-ruta se interroga antes de buscar sys.path, por lo que siempre existe la oportunidad de tener un importador central de módulos de carga sin modificar explícitamente sys.path. Una vez que se encuentra el módulo, la interfaz de programación del cargador funciona de la misma manera que para los cargadores regulares (aunque este ejemplo se trunca para simplificar).

$ python3 sys_meta_path.py

Creating NoisyMetaImportFinder for foo

looking for 'foo' with path None
 ... found prefix, returning loader
loading foo

looking for 'foo.bar' with path ['path-entry-goes-here']
 ... found prefix, returning loader
loading foo.bar

looking for 'bar' with path None
 ... not the right prefix, cannot load

Ver también