大師兄的Python源碼學(xué)習(xí)筆記(三十七): 模塊的動(dòng)態(tài)加載機(jī)制(四)

大師兄的Python源碼學(xué)習(xí)筆記(三十六): 模塊的動(dòng)態(tài)加載機(jī)制(三)
大師兄的Python源碼學(xué)習(xí)筆記(三十八): 模塊的動(dòng)態(tài)加載機(jī)制(五)

三弧岳、import機(jī)制的實(shí)現(xiàn)

1. 找到module/package
Python\import.c

PyObject *
PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
                                 PyObject *locals, PyObject *fromlist,
                                 int level)
{
    _Py_IDENTIFIER(_handle_fromlist);
    PyObject *abs_name = NULL;
    PyObject *final_mod = NULL;
    PyObject *mod = NULL;
    PyObject *package = NULL;
    PyInterpreterState *interp = PyThreadState_GET()->interp;
    int has_from;
    ... ...

        mod = import_find_and_load(abs_name);
    ... ...
  • 在經(jīng)過(guò)一系列判斷后蚂斤,調(diào)用核心函數(shù)import_find_and_load
Python\import.c

static PyObject *
import_find_and_load(PyObject *abs_name)
{
    _Py_IDENTIFIER(_find_and_load);
    PyObject *mod = NULL;
    PyInterpreterState *interp = PyThreadState_GET()->interp;
    int import_time = interp->core_config.import_time;
    static int import_level;
    static _PyTime_t accumulated;

    ... ...

    mod = _PyObject_CallMethodIdObjArgs(interp->importlib,
                                        &PyId__find_and_load, abs_name,
                                        interp->import_func, NULL);

    ... ...

    return mod;
}
  • 跳轉(zhuǎn)到Python并調(diào)用Python函數(shù)_find_and_load_unlocked:
Lib\importlib\_bootstrap.py

def _find_and_load_unlocked(name, import_):
    path = None
    parent = name.rpartition('.')[0]
    if parent:
        if parent not in sys.modules:
            _call_with_frames_removed(import_, parent)
        # Crazy side-effects!
        if name in sys.modules:
            return sys.modules[name]
        parent_module = sys.modules[parent]
        try:
            path = parent_module.__path__
        except AttributeError:
            msg = (_ERR_MSG + '; {!r} is not a package').format(name, parent)
            raise ModuleNotFoundError(msg, name=name) from None
    spec = _find_spec(name, path)
    if spec is None:
        raise ModuleNotFoundError(_ERR_MSG.format(name), name=name)
    else:
        module = _load_unlocked(spec)
    if parent:
        # Set the module as an attribute on its parent.
        parent_module = sys.modules[parent]
        setattr(parent_module, name.rpartition('.')[2], module)
    return module
  • _find_and_load_unlocked函數(shù)會(huì)通過(guò)_find_spec函數(shù)找到需要加載的模塊在哪里,然后調(diào)用_load_unlocked函數(shù)來(lái)加載模塊庙洼。
  • 首先仔細(xì)觀察_find_spec函數(shù):
Lib\importlib\_bootstrap.py

def _find_spec(name, path, target=None):
    """Find a module's spec."""
    meta_path = sys.meta_path
    if meta_path is None:
        # PyImport_Cleanup() is running or has been called.
        raise ImportError("sys.meta_path is None, Python is likely "
                          "shutting down")

    if not meta_path:
        _warnings.warn('sys.meta_path is empty', ImportWarning)

    # We check sys.modules here for the reload case.  While a passed-in
    # target will usually indicate a reload there is no guarantee, whereas
    # sys.modules provides one.
    is_reload = name in sys.modules
    for finder in meta_path:
        with _ImportLockContext():
            try:
                find_spec = finder.find_spec
            except AttributeError:
                spec = _find_spec_legacy(finder, name, path)
                if spec is None:
                    continue
            else:
                spec = find_spec(name, path, target)
        if spec is not None:
            # The parent import may have already imported this module.
            if not is_reload and name in sys.modules:
                module = sys.modules[name]
                try:
                    __spec__ = module.__spec__
                except AttributeError:
                    # We use the found spec since that is the one that
                    # we would have used if the parent module hadn't
                    # beaten us to the punch.
                    return spec
                else:
                    if __spec__ is None:
                        return spec
                    else:
                        return __spec__
            else:
                return spec
    else:
        return None
  • _find_spec函數(shù)會(huì)遍歷sys.meta_path, 對(duì)其中的每一個(gè)對(duì)象調(diào)用findspec函數(shù)培己。
  • 查看sys.meta_path
>>> sys.meta_path
[<class '_frozen_importlib.BuiltinImporter'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib_external.PathFinder'>]
  • Python模塊按導(dǎo)入方式一共分為六種:

1.builtin module loader
2.frozen module loader
3.source file loader
4.sourceless file loader
5.extension module loader
6.zipimport loader

  • 可以看到sys.meta_path包含的三個(gè)對(duì)象: 其中BuiltinImporterFrozenImporter分別對(duì)應(yīng)模塊加載的前兩種方法依溯,而后四種加載方法都是由PathFinder實(shí)現(xiàn)的。
  • 查看PathFinder中的find_spec方法:
Lib\importlib\_bootstrap_external.py

class PathFinder:

    """Meta path finder for sys.path and package __path__ attributes."""

    ... ...

    @classmethod
    def find_spec(cls, fullname, path=None, target=None):
        """Try to find a spec for 'fullname' on sys.path or 'path'.

        The search is based on sys.path_hooks and sys.path_importer_cache.
        """
        if path is None:
            path = sys.path
        spec = cls._get_spec(fullname, path, target)
        if spec is None:
            return None
        elif spec.loader is None:
            namespace_path = spec.submodule_search_locations
            if namespace_path:
                # We found at least one namespace path.  Return a spec which
                # can create the namespace package.
                spec.origin = None
                spec.submodule_search_locations = _NamespacePath(fullname, namespace_path, cls._get_spec)
                return spec
            else:
                return None
        else:
            return spec

    ... ...
  • PathFinder.findspec會(huì)對(duì)sys.path_hooks里的每一個(gè)hook函數(shù)分別調(diào)用sys.path里的每一個(gè)路徑葡粒。
  • 如果某一個(gè)hook函數(shù)在指定的路徑下發(fā)現(xiàn)了指定的包份殿,它就會(huì)返回一個(gè)_bootstrap.ModuleSpec對(duì)象膜钓。
  • _bootstrap.ModuleSpec中會(huì)有一個(gè)loader對(duì)象負(fù)責(zé)真正加載這個(gè)包:
Lib\importlib\_bootstrap_external.py

def _get_supported_file_loaders():
    """Returns a list of file-based module loaders.

    Each item is a tuple (loader, suffixes).
    """
    extensions = ExtensionFileLoader, _imp.extension_suffixes()
    source = SourceFileLoader, SOURCE_SUFFIXES
    bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES
    return [extensions, source, bytecode]
  • PathFinder返回的ModuleSpec可能的loader有以下三種:ExtensionFileLoader, SourceFileLoader, SourcelessFileLoader
  • 接著觀察_load_unlocked函數(shù):
Lib\importlib\_bootstrap.py

def _load_unlocked(spec):
    # A helper for direct use by the import system.
    if spec.loader is not None:
        # not a namespace package
        if not hasattr(spec.loader, 'exec_module'):
            return _load_backward_compatible(spec)

    module = module_from_spec(spec)
    with _installed_safely(module):
        if spec.loader is None:
            if spec.submodule_search_locations is None:
                raise ImportError('missing loader', name=spec.name)
            # A namespace package so do nothing.
        else:
            spec.loader.exec_module(module)

    # We don't ensure that the import-related module attributes get
    # set in the sys.modules replacement case.  Such modules are on
    # their own.
    return sys.modules[spec.name]
  • _load_unlocked函數(shù)創(chuàng)建了一個(gè)空的module對(duì)象卿嘲,然后將這個(gè)空moudle對(duì)象作為參數(shù)調(diào)用了具體loader的exec_module函數(shù)颂斜。
  • 回到作為exec_module函數(shù)的ExtensionFileLoader類:
Lib\importlib\_bootstrap_external.py

class ExtensionFileLoader(FileLoader, _LoaderBasics):

    """Loader for extension modules.

    The constructor is designed to work with FileFinder.

    """

    ... ...

    def create_module(self, spec):
        """Create an unitialized extension module"""
        module = _bootstrap._call_with_frames_removed(
            _imp.create_dynamic, spec)
        _bootstrap._verbose_message('extension module {!r} loaded from {!r}',
                         spec.name, self.path)
        return module
    ... ...
  • ExtensionFileLoader中調(diào)用了builtin對(duì)象_imp.create_dynamic,它的實(shí)現(xiàn)在C語(yǔ)言函數(shù)_imp_create_dynamic_impl
Python\import.c

static PyObject *
_imp_create_dynamic_impl(PyObject *module, PyObject *spec, PyObject *file)
/*[clinic end generated code: output=83249b827a4fde77 input=c31b954f4cf4e09d]*/
{
    PyObject *mod, *name, *path;
    FILE *fp;

    ... ...

    mod = _PyImport_LoadDynamicModuleWithSpec(spec, fp);

    Py_DECREF(name);
    Py_DECREF(path);
    if (fp)
        fclose(fp);
    return mod;
}

  • 最后進(jìn)入_PyImport_LoadDynamicModuleWithSpec函數(shù):
PyObject *
_PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *fp)
{
... ...

#ifdef MS_WINDOWS
    exportfunc = _PyImport_FindSharedFuncptrWindows(hook_prefix, name_buf,
                                                    path, fp);
#else
    pathbytes = PyUnicode_EncodeFSDefault(path);
    if (pathbytes == NULL)
        goto error;
    exportfunc = _PyImport_FindSharedFuncptr(hook_prefix, name_buf,
                                             PyBytes_AS_STRING(pathbytes),
                                             fp);
    Py_DECREF(pathbytes);
#endif

    ... ...

    p0 = (PyObject *(*)(void))exportfunc;

    /* Package context is needed for single-phase init */
    oldcontext = _Py_PackageContext;
    _Py_PackageContext = PyUnicode_AsUTF8(name_unicode);
    if (_Py_PackageContext == NULL) {
        _Py_PackageContext = oldcontext;
        goto error;
    }
    m = p0();
   ... ...
}
  • 最終程序?qū)⒄{(diào)用dlopen/LoadLibrary來(lái)加載動(dòng)態(tài)鏈接庫(kù)并且執(zhí)行其中的PyInit_modulename。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拾枣,一起剝皮案震驚了整個(gè)濱河市沃疮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梅肤,老刑警劉巖司蔬,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異凭语,居然都是意外死亡葱她,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門似扔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吨些,“玉大人,你說(shuō)我怎么就攤上這事炒辉『朗” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵黔寇,是天一觀的道長(zhǎng)偶器。 經(jīng)常有香客問(wèn)我,道長(zhǎng)缝裤,這世上最難降的妖魔是什么屏轰? 我笑而不...
    開(kāi)封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮憋飞,結(jié)果婚禮上霎苗,老公的妹妹穿的比我還像新娘。我一直安慰自己榛做,他們只是感情好唁盏,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著检眯,像睡著了一般厘擂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锰瘸,一...
    開(kāi)封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天刽严,我揣著相機(jī)與錄音,去河邊找鬼获茬。 笑死港庄,一個(gè)胖子當(dāng)著我的面吹牛倔既,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鹏氧,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼渤涌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了把还?” 一聲冷哼從身側(cè)響起实蓬,我...
    開(kāi)封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吊履,沒(méi)想到半個(gè)月后安皱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡艇炎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年酌伊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缀踪。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡居砖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驴娃,到底是詐尸還是另有隱情奏候,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布唇敞,位于F島的核電站蔗草,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疆柔。R本人自食惡果不足惜咒精,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旷档。 院中可真熱鬧狠轻,春花似錦、人聲如沸彬犯。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谐区。三九已至,卻和暖如春逻卖,著一層夾襖步出監(jiān)牢的瞬間宋列,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工评也, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炼杖,地道東北人灭返。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像坤邪,于是被迫代替她去往敵國(guó)和親熙含。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容