# 模块加载和搜索

# 模块加载机制

  • Python内部会用一个dict对象保存所有已经加载过的模块

    >>> import sys
    >>> for name, module in sys.modules.items():
    ...     print(name)
    ... 
    sys
    builtins
    # ...
    
  • 当Python加载模块前,会先检查sys.modules​​,如果发现目标模块已经加载过,则直接将其返回。

  • 因此无论一个模块被多少个import​​语句导入,第一次加载后便不再重复加载。

  • 同时还有一个sys.path​​列表维护模块搜索的路径,其中第一个元素是一个空字符串表示main​​包所在的目录-即入口文件所在的目录

  • 当目标模块找到后,Python对代码进行编程生成PyCodeObject​,如果存在pyc文件则可以省略编译步骤转而直接从pyc文件中加载PyCodeObject​。

    # 读取模块代码
    text = read('demo.py')
    
    # 编译模块代码
    code = compile(text, 'demo.py', 'exec')
    
  • 进而Python创建一个全新的PyModuleObject​模块对象-只提供模块名和模块文档信息。

    demo = module('demo', 'A test module')
    
  • 然后Python执行模块代码对象,完成模块初始化

    exec(code, demo.__dict__, demo.__dict__)
    
  • 最后将模块对象保存到sys.modules​避免重复加载

    import sys
    sys.modules['demo'] = demo
    

image

# 模块加载方式字节码

# import

import demo

  1           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (None)
              4 IMPORT_NAME              0 (demo)
              6 STORE_NAME               0 (demo)
              8 LOAD_CONST               1 (None)
             10 RETURN_VALUE

image

# import as

import demo as d
# 等价于
import demo
d = demo
del demo


  1           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (None)
              4 IMPORT_NAME              0 (demo)
              6 STORE_NAME               1 (d)
              8 LOAD_CONST               1 (None)
             10 RETURN_VALUE

image

# from import

from demo import value
# 等价于
import demo
value = demo.value
del demo

  1           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (('value',))
              4 IMPORT_NAME              0 (demo)
              6 IMPORT_FROM              1 (value)
              8 STORE_NAME               1 (value)
             10 POP_TOP
             12 LOAD_CONST               2 (None)
             14 RETURN_VALUE

image

# from import as

from demo import value as v

  1           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (('value',))
              4 IMPORT_NAME              0 (demo)
              6 IMPORT_FROM              1 (value)
              8 STORE_NAME               2 (v)
             10 POP_TOP
             12 LOAD_CONST               2 (None)
             14 RETURN_VALUE

# 模块加载流程

# IMPORT_NAME

        TARGET(IMPORT_NAME) {
            PyObject *name = GETITEM(names, oparg); // 需要加载的模块名
            PyObject *fromlist = POP(); // 参数列表-即需要记载的潜在子模块
            PyObject *level = TOP();
            PyObject *res;
            res = import_name(f, name, fromlist, level);
            Py_DECREF(level);
            Py_DECREF(fromlist);
            SET_TOP(res);	// 释放参数并将加载到的模块对象保存到栈顶
            if (res == NULL)
                goto error;
            DISPATCH();
        }

# PyImport_ImportModuleLevelObject

PyObject *
PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
                                 PyObject *locals, PyObject *fromlist,
                                 int level);
// 需要传入globals是因为需要据此去计算模块绝对路径(如果使用了相对路径)-模块名存在globals名字空间中