Python模块动态加载机制

本文和大家分享的主要是python中模块动态加载机制相关内容,一起来看看吧,希望对大家学习python有所帮助。

import 指令

来看看 import sys 所产生的指令:

co_consts : (0, None)

co_names : (’sys’,)

0 LOAD_CONST               0 (0)

2 LOAD_CONST               1 (None)

4 IMPORT_NAME              0 (sys)

6 STORE_NAME               0 (sys)

可以看到import的结果是赋值给变量 sys 并存储在当前frame的local名字空间中, 使得后续使用 sys.path 就能很快找到这个符号了. 具体看看 IMPORT_NAME 指令做了什么动作:

TARGET(IMPORT_NAME) {

PyObject *name = GETITEM(names, oparg); // 获得 ’sys’ PyUnicodeObject

PyObject *fromlist = POP();         // None

PyObject *level = TOP();            // 0

PyObject *res;

res = import_name(f, name, fromlist, level);

Py_DECREF(level);

Py_DECREF(fromlist);

SET_TOP(res);

if (res == NULL)

goto error;

DISPATCH();

}

这部分是收集将要import操作的所需信息, 然后调用 import_name :

[ceval.c]

static PyObject * import_name(PyFrameObject *f, PyObject *name, PyObject *fromlist, PyObject *level)

{

_Py_IDENTIFIER(__import__);

PyObject *import_func, *res;

PyObject* stack[5];

// 获得内建函数 __import__

import_func = _PyDict_GetItemId(f->f_builtins, &PyId___import__);

/* 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;

}

...

}

传进来的参数列表分别是, 当前frame的PyFrameObject对象, 表示’sys’的PyUnicodeObject, Py_None对象, 表示0的PyLongObject. 首先从内建 f->builtins 中获取 __import__ 函数. 此时它已经是一个包装过了的PyCFunctionObject对象了,在上一篇的builtins初始化时对每一个方法进行了包装.

if (import_func == PyThreadState_GET()->interp->import_func) 是用来判断 __import__ 是否被程序员重载了, 这里不考虑被重载的情况. PyImport_ImportModuleLevelObject 函数内比较复杂, 因为它还要处理如 import xml.sax 这样的结构, 好像调用时 PyImport_ImportModuleLevelObject 压根没有用到 import_func 这个内建 import 方法, 但其实他们是殊途同归的, 还记得内建方法的数组 builtin_methods 吗:

[bltinmodule.c]

static PyMethodDef builtin_methods[] = {

...

{"__import__",      (PyCFunction)builtin___import__, METH_VARARGS | METH_KEYWORDS, import_doc},

...

};

static PyObject * builtin___import__(PyObject *self, PyObject *args, PyObject *kwds)

{

static char *kwlist[] = {"name", "globals", "locals", "fromlist",

"level", 0};

PyObject *name, *globals = NULL, *locals = NULL, *fromlist = NULL;

int level = 0;

if (!PyArg_ParseTupleAndKeywords(args, kwds, "U|OOOi:__import__",

kwlist, &name, &globals, &locals, &fromlist, &level))

return NULL;

return PyImport_ImportModuleLevelObject(name, globals, locals,

fromlist, level);

}

最终调用的其实是同一个 PyImport_ImportModuleLevelObject 函数.

很混乱有没有, 好, 重新整理下import的实现过程.

import 机制

对于任何import操作, python虚拟机都要做到:

对运行时sys.modules全局模块池的维护

解析和搜索module路径

对不同文件的module的动态加载机制

import的形式也有很多种, 最简单的形式如 import os , 复杂一点就是 import x.y.z , 注入 from和as与import结合的, 也会被分析后变成 import x.y.z 的形式. 所以我们分析import的实现代码就以 import x.y.z 作为指令动作.

当以 import x.y.z 形式时, 调用的参数分别是:

res = PyImport_ImportModuleLevelObject(

name,                                       // 表示 ’x.y.z’ 的 PyUnicodeObject

f->f_globals,                               // frame的global名字空间

f->f_locals == NULL ? Py_None : f->f_locals,// frame的local名字空间

fromlist,                                   // None值

ilevel);                                    // 0

深入这个函数来看:

PyObject * PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,

PyObject *locals, PyObject *fromlist,

int level)

{

_Py_IDENTIFIER(_find_and_load);

_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;

abs_name = name;

mod = PyDict_GetItem(interp->modules, abs_name);

if (mod != NULL && mod != Py_None) {    // 如果全局modules里已经有了, 说明重复引入模块

...

}

}

else { // 该模块第一次引入

mod = _PyObject_CallMethodIdObjArgs(interp->importlib,

&PyId__find_and_load, abs_name,

interp->import_func, NULL);

}

// 处理from xxx import xxx 语句

has_from = 0;

if (fromlist != NULL && fromlist != Py_None) {

has_from = PyObject_IsTrue(fromlist);

if (has_from < 0)

goto error;

}

if (!has_from) {    // 不是from xxx形式的

Py_ssize_t len = PyUnicode_GET_LENGTH(name);

if (level == 0 || len > 0) {

Py_ssize_t dot;

// 查找是模块名是否含有. 不含返回-1, 含会返回其索引

dot = PyUnicode_FindChar(name, ’.’, 0, len, 1);

if (dot == -1) {

/* No dot in module name, simple exit */

final_mod = mod;

Py_INCREF(mod);

goto error;

}

if (level == 0) {

PyObject *front = PyUnicode_Substring(name, 0, dot);

if (front == NULL) {

goto error;

}

final_mod = PyImport_ImportModuleLevelObject(front, NULL, NULL, NULL, 0);

Py_DECREF(front);

}

else {

...

}

}

else {

final_mod = mod;

Py_INCREF(mod);

}

}

else {

final_mod = _PyObject_CallMethodIdObjArgs(interp->importlib,

&PyId__handle_fromlist, mod,

fromlist, interp->import_func,

NULL);

}

error:

return final_mod;

}

这时动态加载就显示出来了, 首先会去全局的 interp->modules 中查看是否已经加载过了该模块, 加载过了就不会重新加载了. 而后处理import语句有含 "." 点的情况, 从代码中可以看到, 如何是 import x.y.z 的形式, 也是会将 x 模块整个引入, 并将它赋值给x (第一个模块).

interp->importlib 是什么呢, 在python初始化中的最后做的一步就是初始化import:

[pylifecycle.c]

void _Py_InitializeCore(const _PyCoreConfig *config)

{

...

_PyImport_Init();

_PyImportHooks_Init();

_PyWarnings_Init();

/* This call sets up builtin and frozen import support */

if (!interp->core_config._disable_importlib) {

printf("interp->core_config._disable_importlib\\n");

initimport(interp, sysmod);

}

_Py_CoreInitialized = 1;

}

这是初始化函数中关于 import 机制的初始化, interp->importlib 就是在 initimport 函数中被赋值的, 当然我们是要从 _PyImport_Init() 开始分析:

[import.c]

static PyObject *initstr = NULL;

void _PyImport_Init(void)

{

PyInterpreterState *interp = PyThreadState_Get()->interp;

initstr = PyUnicode_InternFromString("__init__");

interp->builtins_copy = PyDict_Copy(interp->builtins);

}

这部分就简单创建了 "__init__" 的PyUnicodeObject对象, 和复制一份内建builtins.

[import.c]

void _PyImportHooks_Init(void)

{

PyObject *v, *path_hooks = NULL;

int err = 0;

/* adding sys.path_hooks and sys.path_importer_cache */

v = PyList_New(0);

PySys_SetObject("meta_path", v);

v = PyDict_New();

PySys_SetObject("path_importer_cache", v);

path_hooks = PyList_New(0);

PySys_SetObject("path_hooks", path_hooks);

Py_DECREF(path_hooks);

}

给sys模块设置锚点, 也就是 sys.path_hooks 和 sys.path_importer_cache .

[import.c]

static void initimport(PyInterpreterState *interp, PyObject *sysmod)

{

PyObject *importlib;

PyObject *impmod;

PyObject *sys_modules;

PyObject *value;

...

importlib = PyImport_AddModule("_frozen_importlib");

interp->importlib = importlib;

interp->import_func = PyDict_GetItemString(interp->builtins, "__import__");

impmod = PyInit_imp();

PyDict_SetItemString(sys_modules, "_imp", impmod);

/* Install importlib as the implementation of import */

value = PyObject_CallMethod(importlib, "_install", "OO", sysmod, impmod);

...

}

原来 interp->importlib 是 _frozen_importlib 模块, 将 impmod 安装到改模块, 使 importlib 作为导入的实现. 而 impmod 的导入过程如果用python语言来表示就如下:

你也许会奇怪, 为什么有 importlib 还要有一个 imp ? 这其实是一个新事物取代旧事物的过程, 自python3.4版本以来, 就不推荐 imp 方式, 正在被慢慢替换成 importlib. 很尴尬的是, 现在importlib的实现代码是个code对象的字节码:

[Python/importlib.h]

const unsigned char _Py_M__importlib[] = {

99,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,

0,64,0,0,0,115,210,1,0,0,100,0,90,0,100,1,

97,1,100,2,100,3,132,0,90,2,100,4,100,5,132,0,

...

};

也就是说看不到最终的执行代码了. 啊, 郁闷啊, 目前居然是这样替换imp的. 好吧好吧, 毕竟替换是有个过程. 就用一个python的伪代码来表示一下:

def __import__(name, globals=None, locals=None, fromlist=None):

# Fast path: see if the module has already been imported.

try:

return sys.modules[name]

except KeyError:

pass

# If any of the following calls raises an exception,

# there’s a problem we can’t handle -- let the caller handle it.

fp, pathname, description = imp.find_module(name)

try:

return imp.load_module(name, fp, pathname, description)

finally:

# Since we may exit via an exception, close fp explicitly.

if fp:

fp.close()

作为全局的 sys.modules 用来记录已经引入的模块, 当判断已经存在时就不需要重新import的.

import 机制的影响

在pythoon的import机制中, 会影响当前的local名字空间. sys.modules 表示全局引入的模块, 有的模块会默认加载到内存(比如 os), 但是通过 dir() 可以看到, 这些并没有在当前的local名字空间中.

而如果是被import文件里面又import其他文件, 名字空间不会影响到上一层的空间.

可以看到当前local名字空间中的 "__builtins__" 是一个module对象, 而test中的 __builtins__ 确实dict对象. 上一篇提过, python进程中仅有一个 builtins , 它被所有线程共享, 这也是其背后的module对象下维护的dict其实指向的是同一个. test modules中的 builtins 符号对应的正式当前名字空间中 builtins 维护的dict对象. 他们背后其实都是python环境初始化中的 builtins module中维护的dict. 这个module早就被python加载进内存, 维护在sys.modules中.

实际上, 所有的import操作, 不管是什么时间, 什么地方, 都是会影响到全局module集合即sys.modules.

来源:栖迟於一丘

时间: 2024-10-06 08:24:45

Python模块动态加载机制的相关文章

《python解释器源码剖析》第15章--python模块的动态加载机制

15.0 序 在之前的章节中,我们考察的东西都是局限在一个模块(在python中就是module)内.然而现实中,程序不可能只有一个模块,更多情况下一个程序会有多个模块,而模块之间存在着引用和交互,这些引用和交互也是程序的一个重要的组成部分.本章剖析的就是在python中,一个模块是如何加载.并引用另一个模块的功能的.对于一个模块,肯定要先从硬盘加载到内存. 15.1 import前奏曲 我们以一个简单的import为序幕 # a.py import sys 1 0 LOAD_CONST 0 (

Android apk动态加载机制的研究(二):资源加载和activity生命周期管理

出处:http://blog.csdn.net/singwhatiwanna/article/details/23387079 (来自singwhatiwanna的csdn博客) 前言 为了更好地阅读本文,你需要先阅读Android apk动态加载机制的研究这篇文章,在此文中,博主分析了Android中apk的动态加载机制,并在文章的最后指出需要解决的两个复杂问题:资源的访问和activity生命周期的管理,而本文将会分析这两个复杂问题的解决方法.需要说明的一点是,我们不可能调起任何一个未安装的

java动态加载机制

假设有一个class,ClassLoader首先把它load到内存里的code segment(内存里存放代码段的),站在ClassLoader的角度,内存里的一个一个的class就是一个一个的对象,这个对象就是xx.class,实际就是Class类的对象.Load完class,找到main函数开始执行,然后会把很多其他的类Load进来,动态加载机制. 测试动态加载机制: 新建项目Reflection,new一个class,TestDynamicLoading: public class Tes

unity 3D里有两种动态加载机制

unity 3D里有两种动态加载机制: 一是Resources.Load: 一是通过AssetBundle: 其实两者本质上没有什么区别.Resources.Load就是从一个缺省打进程序包里的AssetBundle里加载资源,而一般AssetBundle文件需要你自己创建,运行时动态加载,可以指定路径和来源的.其实场景里所有静态的对象也有这么一个加载过程,只是Unity后台替你自动完成了. 1.    AssetBundles是什么? 在一些大型的网络游戏,或者加载比较多的一些场景时,如果要等

nodejs 模块以及加载机制,主要讨论找不到模块的问题

最主要的一个思想,加载模块无非就是找到模块在哪,只要清楚了模块的位置以及模块加载的逻辑那么找不到模块的问题就迎刃而解了.本文只是综合了自己所学的知识点进行总结,难免出现理解错误的地方,请见谅. nodejs的模块分类 1.原生模块:http  fs path等,这些模块都在源码包的lib目录下面,nodejs安装好之后是找不到这些模块的,都作为node.exe的一部分了,require这些模块永远没问题的,如果哪天出现问题了,直接重启电脑或者重装node.有什么疑问可以通过下载源码对这些原生模块

虚幻4的关卡动态加载机制

对于大型游戏MMORPG游戏或是3D街景(虚拟现实)还有大型无缝地图都需要地图啊,关卡动态加载的机制来达到让用户感觉自己一直是在漫游,而不会出现Loading(读条),卡界面等情况.当然除非是垮区域无法依靠关卡和关卡中之间的关系做处理. UnrealEngine4 作为一个多年处于世界前矛的商业引擎,自然而然也为大家考虑到这个这种硬性需求.UnrealEngine4 中的动态加载大概分为3种. 首先需要明确下几点概念.因为再同学们信息会等的前提下才会更有利于理解和相互交流.好了,不瞎扯了进入正题

Android中的动态加载机制

在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势.本文对网上Android动态加载jar的资料进行梳理和实践在这里与大家一起分享,试图改善频繁升级这一弊病. Android应用开发在一般情况下,常规的开发方式和代码架构就能满足我们的普通需求.但是有些特殊问题,常常引发我们进一步的沉思.我们从沉思中产生顿悟,从而产生新的技术形式.如何开发一个可以自定义

Android中的动态加载机制--薛彦顺

在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势.本文对网上Android动态加载jar的资料进行梳理和实践在这里与大家一起分享,试图改善频繁升级这一弊病. Android应用开发在一般情况下,常规的开发方式和代码架构就能满足我们的普通需求.但是有些特殊问题,常常引发我们进一步的沉思.我们从沉思中产生顿悟,从而产生新的技术形式. 如何开发一个可以自定

nginx模块动态加载(http)

解析部分 动态加载所有模块,维护一个全局数组modules[nmodules]: 解析配置文件,记录每个块及子块所用的模块,细分到location级别(merge),维护一个全局数组call_modulers[nlocations][ncallmodulers],每当生成一个location,nlocations++: 为call_modulers中每个location添加默认模块及配置,所有模块加载完毕: merge call_modulers中各个location使用模块的phase的cal