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.
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
.
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
- Instrucciones de construcción – Instrucciones para construir Python, del README distribuido con el código fuente
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.
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.
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.
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.
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.
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:
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.
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.
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
.
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).
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
– Includesget_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.
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.
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
importlib
– Clases base y otras herramientas para crear importadores personalizados.zipimport
– Implementa la importación de módulos Python desde archivos ZIP.- The Internal Structure of Python Eggs – documentación de setuptools para el formato egg
- Wheel – Documentación del
formato de archivo
wheel
para el código Python instalable. - PEP 302 – Ganchos de importación
- PEP 366 – Importaciones relativas explícitas del módulo principal
- PEP 427 – El formato del paquete Wheel Binario 1.0
- Import this, that, and the other thing: custom importers – Presentación PyCon 2010 de Brett Cannon.