美文网首页py
大师兄的Python源码学习笔记(三十八): 模块的动态加载机制

大师兄的Python源码学习笔记(三十八): 模块的动态加载机制

作者: superkmi | 来源:发表于2021-10-22 08:52 被阅读0次

    大师兄的Python源码学习笔记(三十七): 模块的动态加载机制(四)
    大师兄的Python源码学习笔记(三十九): Python的多线程机制(一)

    四、Python中的import操作

    • 本章将研究import操作所对应的字节码指令序列。
    1. import module
    demo.py
    
    >>>import sys
    
      1           0 LOAD_CONST               0 (0)
                  2 LOAD_CONST               1 (None)
                  4 IMPORT_NAME              0 (sys)
                  6 STORE_NAME               0 (sys)
                  8 LOAD_CONST               1 (None)
                 10 RETURN_VALUE
    
    • 这部分代码前面的章节已经涉及到,最终IMPORT_NAME会将加载后的PyModuleObject对象压到运行时栈内。
    ceval.c
    
    import_name(PyFrameObject *f, PyObject *name, PyObject *fromlist, PyObject *level)
    {
        _Py_IDENTIFIER(__import__);
        PyObject *import_func, *res;
        PyObject* stack[5];
    
        import_func = _PyDict_GetItemId(f->f_builtins, &PyId___import__);
        if (import_func == NULL) {
            PyErr_SetString(PyExc_ImportError, "__import__ not found");
            return NULL;
        }
    
        /* Fast path for not overloaded __import__. */
        if (import_func == PyThreadState_GET()->interp->import_func) {
            int ilevel = _PyLong_AsInt(level);
            if (ilevel == -1 && PyErr_Occurred()) {
                return NULL;
            }
            res = PyImport_ImportModuleLevelObject(
                            name,
                            f->f_globals,
                            f->f_locals == NULL ? Py_None : f->f_locals,
                            fromlist,
                            ilevel);
            return res;
        }
    
        Py_INCREF(import_func);
    
        stack[0] = name;
        stack[1] = f->f_globals;
        stack[2] = f->f_locals == NULL ? Py_None : f->f_locals;
        stack[3] = fromlist;
        stack[4] = level;
        res = _PyObject_FastCall(import_func, stack, 5);
        Py_DECREF(import_func);
        return res;
    }
    
    2. import package
    demo.py 
    
    >>>import os.path
    
      1           0 LOAD_CONST               0 (0)
                  2 LOAD_CONST               1 (None)
                  4 IMPORT_NAME              0 (os.path)
                  6 STORE_NAME               1 (os)
                  8 LOAD_CONST               1 (None)
                 10 RETURN_VALUE
    
    • 如果import的动作涉及package,那么IMPORT_NAME指令的指令参数将是module的完整路径信息。
    • IMPORT_NAME指令内部将解析出绝对路径。
    Python\import.c
    
    static PyObject *
    resolve_name(PyObject *name, PyObject *globals, int level)
    {
        _Py_IDENTIFIER(__spec__);
        _Py_IDENTIFIER(__package__);
        _Py_IDENTIFIER(__path__);
        _Py_IDENTIFIER(__name__);
        _Py_IDENTIFIER(parent);
        PyObject *abs_name;
        PyObject *package = NULL;
        PyObject *spec;
        Py_ssize_t last_dot;
        PyObject *base;
        int level_up;
    
        if (globals == NULL) {
            PyErr_SetString(PyExc_KeyError, "'__name__' not in globals");
            goto error;
        }
        if (!PyDict_Check(globals)) {
            PyErr_SetString(PyExc_TypeError, "globals must be a dict");
            goto error;
        }
        package = _PyDict_GetItemId(globals, &PyId___package__);
        if (package == Py_None) {
            package = NULL;
        }
        spec = _PyDict_GetItemId(globals, &PyId___spec__);
    
        if (package != NULL) {
            Py_INCREF(package);
            if (!PyUnicode_Check(package)) {
                PyErr_SetString(PyExc_TypeError, "package must be a string");
                goto error;
            }
            else if (spec != NULL && spec != Py_None) {
                int equal;
                PyObject *parent = _PyObject_GetAttrId(spec, &PyId_parent);
                if (parent == NULL) {
                    goto error;
                }
    
                equal = PyObject_RichCompareBool(package, parent, Py_EQ);
                Py_DECREF(parent);
                if (equal < 0) {
                    goto error;
                }
                else if (equal == 0) {
                    if (PyErr_WarnEx(PyExc_ImportWarning,
                            "__package__ != __spec__.parent", 1) < 0) {
                        goto error;
                    }
                }
            }
        }
        else if (spec != NULL && spec != Py_None) {
            package = _PyObject_GetAttrId(spec, &PyId_parent);
            if (package == NULL) {
                goto error;
            }
            else if (!PyUnicode_Check(package)) {
                PyErr_SetString(PyExc_TypeError,
                        "__spec__.parent must be a string");
                goto error;
            }
        }
        else {
            if (PyErr_WarnEx(PyExc_ImportWarning,
                        "can't resolve package from __spec__ or __package__, "
                        "falling back on __name__ and __path__", 1) < 0) {
                goto error;
            }
    
            package = _PyDict_GetItemId(globals, &PyId___name__);
            if (package == NULL) {
                PyErr_SetString(PyExc_KeyError, "'__name__' not in globals");
                goto error;
            }
    
            Py_INCREF(package);
            if (!PyUnicode_Check(package)) {
                PyErr_SetString(PyExc_TypeError, "__name__ must be a string");
                goto error;
            }
    
            if (_PyDict_GetItemId(globals, &PyId___path__) == NULL) {
                Py_ssize_t dot;
    
                if (PyUnicode_READY(package) < 0) {
                    goto error;
                }
    
                dot = PyUnicode_FindChar(package, '.',
                                            0, PyUnicode_GET_LENGTH(package), -1);
                if (dot == -2) {
                    goto error;
                }
    
                if (dot >= 0) {
                    PyObject *substr = PyUnicode_Substring(package, 0, dot);
                    if (substr == NULL) {
                        goto error;
                    }
                    Py_SETREF(package, substr);
                }
            }
        }
    
        last_dot = PyUnicode_GET_LENGTH(package);
        if (last_dot == 0) {
            PyErr_SetString(PyExc_ImportError,
                    "attempted relative import with no known parent package");
            goto error;
        }
    
        for (level_up = 1; level_up < level; level_up += 1) {
            last_dot = PyUnicode_FindChar(package, '.', 0, last_dot, -1);
            if (last_dot == -2) {
                goto error;
            }
            else if (last_dot == -1) {
                PyErr_SetString(PyExc_ValueError,
                                "attempted relative import beyond top-level "
                                "package");
                goto error;
            }
        }
    
        base = PyUnicode_Substring(package, 0, last_dot);
        Py_DECREF(package);
        if (base == NULL || PyUnicode_GET_LENGTH(name) == 0) {
            return base;
        }
    
        abs_name = PyUnicode_FromFormat("%U.%U", base, name);
        Py_DECREF(base);
        return abs_name;
    
      error:
        Py_XDECREF(package);
        return NULL;
    }
    
    3. from a import b
    demo.py
    
    >>>from os import path
    
      1           0 LOAD_CONST               0 (0)
                  2 LOAD_CONST               1 (('path',))
                  4 IMPORT_NAME              0 (os)
                  6 IMPORT_FROM              1 (path)
                  8 STORE_NAME               1 (path)
                 10 POP_TOP
                 12 LOAD_CONST               2 (None)
                 14 RETURN_VALUE
    
    • 如果包含from,字节码指令中的LOAD_CONST 1指令参数将不再是None,而是一个tuple对象,也就是源码中的from_list
    • 随后IMPORT_NAME最终将返回os对应的module对象,供IMPORT_FROM使用:
    ceval.c
    
    TARGET(IMPORT_FROM) {
                PyObject *name = GETITEM(names, oparg);
                PyObject *from = TOP();
                PyObject *res;
                res = import_from(from, name);
                PUSH(res);
                if (res == NULL)
                    goto error;
                DISPATCH();
            }
    
    ceval.c
    
    static PyObject *
    import_from(PyObject *v, PyObject *name)
    {
        PyObject *x;
        _Py_IDENTIFIER(__name__);
        PyObject *fullmodname, *pkgname, *pkgpath, *pkgname_or_unknown, *errmsg;
    
       ... ...
        x = PyImport_GetModule(fullmodname);
        Py_DECREF(fullmodname);
       ... ...
        return x;
    ... ...
    }
    
    • IMPORT_FROM会在IMPORT_NAME结果对应module对象的名字空间中搜索符号,并将结果捏合在一起存放在当前local名字空间中。
    4. from a import *
    demo.py
    
    >>>from os import *
    
      1           0 LOAD_CONST               0 (0)
                  2 LOAD_CONST               1 (('*',))
                  4 IMPORT_NAME              0 (os)
                  6 IMPORT_STAR
                  8 LOAD_CONST               2 (None)
                 10 RETURN_VALUE
    
    • 如果from搜索的结果为*,最大的区别是IMPORT_STAR替换了IMPORT_FROM
    ceval.c
    
    TARGET(IMPORT_STAR) {
                PyObject *from = POP(), *locals;
                int err;
                if (PyFrame_FastToLocalsWithError(f) < 0) {
                    Py_DECREF(from);
                    goto error;
                }
    
                locals = f->f_locals;
                if (locals == NULL) {
                    PyErr_SetString(PyExc_SystemError,
                        "no locals found during 'import *'");
                    Py_DECREF(from);
                    goto error;
                }
                err = import_all_from(locals, from);
                PyFrame_LocalsToFast(f, 0);
                Py_DECREF(from);
                if (err != 0)
                    goto error;
                DISPATCH();
            }
    
    ceval.c
    
    static int
    import_all_from(PyObject *locals, PyObject *v)
    {
        _Py_IDENTIFIER(__all__);
        _Py_IDENTIFIER(__dict__);
        PyObject *all, *dict, *name, *value;
        int skip_leading_underscores = 0;
        int pos, err;
    
        if (_PyObject_LookupAttrId(v, &PyId___all__, &all) < 0) {
            return -1; /* Unexpected error */
        }
        if (all == NULL) {
            if (_PyObject_LookupAttrId(v, &PyId___dict__, &dict) < 0) {
                return -1;
            }
            if (dict == NULL) {
                PyErr_SetString(PyExc_ImportError,
                        "from-import-* object has no __dict__ and no __all__");
                return -1;
            }
            all = PyMapping_Keys(dict);
            Py_DECREF(dict);
            if (all == NULL)
                return -1;
            skip_leading_underscores = 1;
        }
    
        for (pos = 0, err = 0; ; pos++) {
            name = PySequence_GetItem(all, pos);
            ... ...
            value = PyObject_GetAttr(v, name);
            if (value == NULL)
                err = -1;
            else if (PyDict_CheckExact(locals))
                err = PyDict_SetItem(locals, name, value);
            else
                err = PyObject_SetItem(locals, name, value);
            Py_DECREF(name);
            Py_XDECREF(value);
            if (err != 0)
                break;
        }
        Py_DECREF(all);
        return err;
    }
    
    • 这种形式使用了module文件中的特殊符号__all__,它可以控制module想要暴露给外界的符号。
    • 最终通过for循环将__all__中name对应的对象与IMPORT_NAME的结果捏合在一起,并存放在当前local名字空间中。
    5. import a as b
    demo.py
    
    >>>import pandas as pd
    
      1           0 LOAD_CONST               0 (0)
                  2 LOAD_CONST               1 (None)
                  4 IMPORT_NAME              0 (pandas)
                  6 STORE_NAME               1 (pd)
                  8 LOAD_CONST               1 (None)
                 10 RETURN_VALUE
    
    • as的重命名机制很简单,就是通过STORE_NAME指令向当前local名字空间引入符号时,加入指定的符号作为参数替换默认符号。
    6. reload
    demo.py
    
    >>>import importlib,os
    >>>importlib.reload(os)
    
      1           0 LOAD_CONST               0 (0)
                  2 LOAD_CONST               1 (None)
                  4 IMPORT_NAME              0 (importlib)
                  6 STORE_NAME               0 (importlib)
                  8 LOAD_CONST               0 (0)
                 10 LOAD_CONST               1 (None)
                 12 IMPORT_NAME              1 (os)
                 14 STORE_NAME               1 (os)
    
      2          16 LOAD_NAME                0 (importlib)
                 18 LOAD_METHOD              2 (reload)
                 20 LOAD_NAME                1 (os)
                 22 CALL_METHOD              1
                 24 POP_TOP
                 26 LOAD_CONST               1 (None)
                 28 RETURN_VALUE
    
    • 可以从源码看出,reload在Python3中实际就是执行了importlib包中的reload函数:
    >>>def reload(module):
        ... ...
    >>>    try:
    >>>        name = module.__spec__.name
    >>>    except AttributeError:
    >>>        name = module.__name__
    
    >>>    if sys.modules.get(name) is not module:
    >>>        msg = "module {} not in sys.modules"
    >>>        raise ImportError(msg.format(name), name=name)
    >>>    if name in _RELOADING:
    >>>        return _RELOADING[name]
    >>>    _RELOADING[name] = module
    >>>    try:
    >>>        parent_name = name.rpartition('.')[0]
    >>>        if parent_name:
    >>>            try:
    >>>                parent = sys.modules[parent_name]
    >>>            except KeyError:
    >>>                msg = "parent {!r} not in sys.modules"
    >>>                raise ImportError(msg.format(parent_name),
    >>>                                  name=parent_name) from None
    >>>            else:
    >>>                pkgpath = parent.__path__
    >>>        else:
    >>>            pkgpath = None
    >>>        target = module
    >>>        spec = module.__spec__ = _bootstrap._find_spec(name, pkgpath, target)
    >>>        if spec is None:
    >>>            raise ModuleNotFoundError(f"spec not found for the module {name!r}", name=name)
    >>>        _bootstrap._exec(spec, module)
    >>>        # The module may have replaced itself in sys.modules!
    >>>        return sys.modules[name]
    >>>    finally:
    >>>        try:
    >>>            del _RELOADING[name]
    >>>        except KeyError:
    >>>            pass
    

    相关文章

      网友评论

        本文标题:大师兄的Python源码学习笔记(三十八): 模块的动态加载机制

        本文链接:https://www.haomeiwen.com/subject/jizgoltx.html