Шаг 197.
Python: сборник рецептов. Модули и пакеты. Загрузка модулей с удаленного компьютера с использованием хуков импортирования. Обсуждение (продолжение)

    На этом шаге мы продолжим обсуждение этого вопроса.

    Тот факт, что вы можете инсталлировать поисковик с целью "отлова" неизвестных модулей, является ключом к пониманию класса UrlMetaFinder в этом рецепте. Экземпляр UrlMetaFinder добавлен в конец sys.metapath, где служит своего рода "импортировщиком последнего шанса". Если запрошенное имя модуля не может быть обнаружено никаким другим механизмом импортирования, оно обрабатывается этим поисковиком. При обработке пакетов нужно принять некоторые предосторожности. Если конкретно, то представленное в аргументе path значение должно быть проверено, чтобы посмотреть, не начинается ли оно с зарегистрированного в поисковике URL. Если нет, подмодуль принадлежит какому-то другому поисковику и должен быть проигнорирован.

    Дополнительная обработка пакетов производится с помощью класса UrlPackageLoader. Этот класс вместо импортирования имени пакета пытается загрузить файл __init__.py. Он также устанавливает атрибут модуля __path__. Эта последняя часть является критически важной, поскольку установленное значение будет передано в последующие вызовы find_module() при загрузке подмодулей пакета.

    Основанный на пути хук импортирования расширяет эти идеи, но базируется на другом механизме. Как вы знаете, sys.path - это список каталогов, где Python ищет модули. Например:

>>> from pprint import pprint
>>> import sys
>>> pprint(sys.path)
['',
 'C:\\Python38\\Lib\\idlelib',
 'C:\\Python38\\python38.zip',
 'C:\\Python38\\DLLs',
 'C:\\Python38\\lib',
 'C:\\Python38',
 'C:\\Python38\\lib\\site-packages']
>>> 

    Каждая запись в sys.path дополнительно прикреплена к объекту поисковика. Вы можете просматривать эти поисковики путем обращения к sys.path_importer_cache:

>>> pprint(sys.path_importer_cache)
{'C:\\Python38': FileFinder('C:\\Python38'),
 'C:\\Python38\\DLLs': FileFinder('C:\\Python38\\DLLs'),
 'C:\\Python38\\Lib\\idlelib': FileFinder('C:\\Python38\\Lib\\idlelib'),
 'C:\\Python38\\lib': FileFinder('C:\\Python38\\lib'),
 'C:\\Python38\\lib\\collections': FileFinder('C:\\Python38\\lib\\collections'),
 'C:\\Python38\\lib\\encodings': FileFinder('C:\\Python38\\lib\\encodings'),
 'C:\\Python38\\lib\\idlelib': FileFinder('C:\\Python38\\lib\\idlelib'),
 'C:\\Python38\\lib\\importlib': FileFinder('C:\\Python38\\lib\\importlib'),
 'C:\\Python38\\lib\\site-packages': FileFinder('C:\\Python38\\lib\\site-packages'),
 'C:\\Python38\\lib\\tkinter': FileFinder('C:\\Python38\\lib\\tkinter'),
 'C:\\Python38\\lib\\urllib': FileFinder('C:\\Python38\\lib\\urllib'),
 'C:\\Python38\\lib\\xml': FileFinder('C:\\Python38\\lib\\xml'),
 'C:\\Python38\\lib\\xml\\parsers': FileFinder('C:\\Python38\\lib\\xml\\parsers'),
 'C:\\Python38\\python38.zip': None}
>>> 

    sys.path_importer_cache обычно намного больше sys.path, потому что в него записываются поисковики для всех известных каталогов, из которых загружается код. Это включает подкаталоги пакетов, которые обычно не включаются в sys.path.

    Чтобы выполнить import fib, каталоги в sys.path проверяются по порядку. Для каждого каталога имя fib представляется ассоциированному поисковику, найденному в sys.path_importer_cache. Вам стоит исследовать этот аспект путем создания собственного поисковика и помещения записи в кеш. Попробуйте провести такой эксперимент:

>>> import sys
>>> # Добавить запись "debug" в кеш импортировщика
>>> sys.path_importer_cache['debug'] = Finder()
>>> # Добавить каталог "debug" в sys.path
>>> sys.path.insert(0, 'debug')
>>> import threading
Looking for threading 
Looking for time 
Looking for traceback 
Looking for linecache
Looking for tokenize 
Looking for token
>>> 

    Здесь вы установили новую запись кеша для имени debug и установили имя debug в качестве первой записи в sys.path. В последующих операциях импортирования вы видите, как запускается ваш поисковик. Однако, поскольку он возвращает (None, []), процесс обработки просто переходит к следующей записи.

    Наполнение sys.path_importer_cache контролируется списком функций, который хранится в sys.path_hooks. Попробуйте провести эксперимент, в котором вы очистите кеш и добавите новую функцию проверки пути в sys.path_hooks:

>>> sys.path_importer_cache.clear()
>>> def check_path(path):
	print('Checking', path)
	raise ImportError()

>>> sys.path_hooks.insert(0, check_path)
>>> import fib
Checking debug
Checking C:\Python38
Checking C:\Python38\Lib\idlelib
Checking C:\Python38\python38.zip
Checking C:\Python38\DLLs
Checking C:\Python38\lib
Checking C:\Python38\lib\site-packages
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    import fib
ModuleNotFoundError: No module named 'fib'
>>> 

    Как вы можете видеть, функция check_path() вызывается для каждой записи из sys.path. Однако, поскольку возбуждается исключение ImportError, ничего другого не происходит (проверка просто переходит к следующей функции в sys.path_ hooks).

    На следующем шаге мы закончим обсуждение этого вопроса.




Предыдущий шаг Содержание Следующий шаг