学习python importlib的导入机制

1. Importer协议

协议涉及两个对象: Finder 和 loader

1. Finder

实现了方法:
finder.find_module(fullname, path=None)

返回一个loader对象或者None。

2. Loader

实现了方法:
loader.load_module(fullname)

返回一个module对象或者raise an exception

参考:pep302

2. 注册hooks

hooks有两种,一种是Meta hooks,一种是Path hooks。
注册方式:

import importlib.machinery
import sys

# For illustrative purposes only.
SpamMetaPathFinder = importlib.machinery.PathFinder
SpamPathEntryFinder = importlib.machinery.FileFinder
loader_details = (importlib.machinery.SourceFileLoader,
                  importlib.machinery.SOURCE_SUFFIXES)

# Setting up a meta path finder.
# Make sure to put the finder in the proper location in the list in terms of
# priority.
sys.meta_path.append(SpamMetaPathFinder)

# Setting up a path entry finder.
# Make sure to put the path hook in the proper location in the list in terms
# of priority.
sys.path_hooks.append(SpamPathEntryFinder.path_hook(loader_details))

参考:
pep302
importlib

3. 流程

import importlib.util
import sys

def import_module(name, package=None):
    """An approximate implementation of import."""
    absolute_name = importlib.util.resolve_name(name, package)
    try:
        return sys.modules[absolute_name]  # 先查询sys.modules
    except KeyError:
        pass

    path = None
    if '.' in absolute_name:
        parent_name, _, child_name = absolute_name.rpartition('.')
        parent_module = import_module(parent_name)
        path = parent_module.__spec__.submodule_search_locations
    for finder in sys.meta_path:  # 再从sys.meta_path中获取finder
        spec = finder.find_spec(absolute_name, path)
        if spec is not None:
            break
    else:
        msg = f'No module named {absolute_name!r}'
        raise ModuleNotFoundError(msg, name=absolute_name)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    sys.modules[absolute_name] = module
    if path is not None:
        setattr(parent_module, child_name, module)
    return module

其中:
sys.meta_path[-1]类型是<class '_frozen_importlib_external.PathFinder'>, 这个对象调用了sys.path以及sys.path_hooks。

注意:
本来按照pep302的导入逻辑,是:

for mp in sys.meta_path:
    loader = mp(fullname)
    if loader is not None:
        <module> = loader.load_module(fullname)
        
for path in sys.path:
    for hook in sys.path_hooks:
        try:
            importer = hook(path)
        except ImportError:
            # ImportError, so try the other path hooks
            pass
        else:
            loader = importer.find_module(fullname)
            <module> = loader.load_module(fullname)

# Not found!
raise ImportError

但后来(python3.4)引入了pep451, 接口名和调用方式发生了一些变化,使得流程如3所示。

主要参考:
pep302
pep451
importlib
New Import Hooks
Python 类库引入机制

原文地址:https://www.cnblogs.com/lyg-blog/p/10642353.html