На этом шаге мы продолжим обсуждение этого вопроса.
Тот факт, что вы можете инсталлировать поисковик с целью "отлова" неизвестных модулей, является ключом к пониманию класса 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).
На следующем шаге мы закончим обсуждение этого вопроса.