使用imp.load_source加载同名模块导致模块合并

9 投票
1 回答
12414 浏览
提问于 2025-04-17 17:08

我想知道下面的行为是正常的还是一个错误。我正在使用 CPython2.7。

首先,创建一个文件 x.py。

def funcA():
    print "funcA of x.py"
def funcB():
    print "funcB of x.py"

然后,创建一个文件 y.py。

def funcB():
    print "funcB of y.py"

接着,创建一个文件 test.py。

import sys, imp
# load x.py as fff
m = imp.load_source('fff', 'x.py')
print dir(m)
print sys.modules.get('fff')
# load y.py as fff
m = imp.load_source('fff', 'y.py')
print dir(m)    
print sys.modules.get('fff')

# import and exec func
import fff
fff.funcA()
fff.funcB()
print dir(fff)

最后,得到的结果是:

['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'funcA', 'funcB']
<module 'fff' from 'x.py'>
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'funcA', 'funcB']
<module 'fff' from 'y.py'>
funcA of x.py
funcB of y.py
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'funcA', 'funcB']

我原本以为第二个 imp.load_source 会完全用 y.py 替换掉 x.py 模块。实际上,sys.modules.get('fff') 显示的是 <module 'fff' from 'y.py'>,但得到的模块却像是 x.py 和 y.py 的混合体,而 y.py 的内容优先。

这是正常现象还是一个错误呢?

补充:我的测试代码有个拼写错误。更新了结果。

1 个回答

15

这是一个正常的行为。
可以查看 http://docs.python.org/2/library/imp.html

imp.load_source(name, pathname[, file])

这个函数的作用是加载并初始化一个用Python源文件实现的模块,并返回这个模块的对象。如果这个模块已经被初始化过了,它会再次被初始化。这里的name参数是用来创建或访问一个模块对象的。

因为你的第二个模块和第一个模块同名,所以它不会替换第一个模块,而是会和第一个模块合并在一起。

源代码给出的解释也是一样的。
imp是一个内置模块,定义在 import.c 中。
我们来看一下 load_source 的定义。

static PyObject *
load_source_module(char *name, char *pathname, FILE *fp)
{
    ......
    m = PyImport_ExecCodeModuleEx(name, (PyObject *)co, pathname);
    Py_DECREF(co);

    return m;
}

它只是一个对 PyImport_ExecCodeModuleEx 的封装。

PyObject *
PyImport_ExecCodeModuleEx(char *name, PyObject *co, char *pathname)
{
    PyObject *modules = PyImport_GetModuleDict();
    PyObject *m, *d, *v;

    m = PyImport_AddModule(name);
    ......
    d = PyModule_GetDict(m);
    ......
    v = PyEval_EvalCode((PyCodeObject *)co, d, d);
    ......
}

现在,我们只需要关注 PyImport_AddModule。Python使用它来获取一个模块对象。你解析的源文件会被放入这个模块对象中。

PyObject *
PyImport_AddModule(const char *name)
{
    PyObject *modules = PyImport_GetModuleDict();
    PyObject *m;

    if ((m = PyDict_GetItemString(modules, name)) != NULL &&
        PyModule_Check(m))
        return m;
    m = PyModule_New(name);
    if (m == NULL)
        return NULL;
    if (PyDict_SetItemString(modules, name, m) != 0) {
        Py_DECREF(m);
        return NULL;
    }
    Py_DECREF(m); /* Yes, it still exists, in modules! */

    return m;
}

最后,我们找到了答案。给定一个 name,如果某个模块已经有这个 name,也就是说,name在sys.modules中,那么Python不会创建一个新的模块,而是会重用那个模块。

撰写回答