使用自定义加载函数时,模块中未定义Python模块

2022-03-31 00:00:00 python python-import

问题描述

考虑使用以下程序加载目录中的所有模块:

import pkgutil
import os.path
import sys

def load_all(directory):
    for loader, name, ispkg in pkgutil.walk_packages([directory]):
        loader.find_module(name).load_module(name)

if __name__ == '__main__':
    here = os.path.dirname(os.path.realpath(__file__))
    path = os.path.join(here, 'lib')
    sys.path.append(path)
    load_all(path)

现在考虑我们有以下文件。

lib/magic/imho.py:

"""This is an empty module.
"""

lib/Magic/wtf.py:

import magic.imho
print "magic contents:", dir(magic)

lib/magic/__init__.py:

"Empty package init file"

执行上述程序时,输出:

magic contents: ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__']

换句话说,尽管import magic.imhoimho上没有导致以后引用magic.imho失败的属性。

直接在python中运行相同的代码(或多或少)会显示不同的输出,这是我在运行加载程序时预期的输出。

$ PYTHONPATH=lib ipython
Python 2.7.6 (default, Mar 22 2014, 22:59:38) 
Type "copyright", "credits" or "license" for more information.

IPython 1.2.1 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: import magic.wtf
magic contents: ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'imho']

为什么会有差异?

更新:这里的要点是magic.imho中不存在符号magic.wft,尽管它是显式导入的。因此,需要对自定义加载过程执行哪些操作才能使其正常运行,并确保符号在magic.wtf中导入后可见。

解决方案使用setattr:BartoszKP给出了下面使用exec为程序包赋值的解决方案。由于我通常不喜欢使用exec(因为很多原因,注射是第一个),所以我使用了setattrrpartition,如下所示:

def load_all(directory):
    for loader, name, ispkg in pkgutil.walk_packages([directory]):
        module = loader.find_module(name).load_module(name)
        pkg_name, _, mod_name = name.rpartition('.')
        if pkg_name:
           setattr(sys.modules[pkg], mod_name, module)

解决方案

如this answer中建议的,为了更准确地模拟import正在执行的操作,您应该将load_all方法修改如下:

def load_all(directory):
    for loader, name, ispkg in pkgutil.walk_packages([directory]):
        module = loader.find_module(name).load_module(name)
        exec('%s = module' % name)

这样工作,wtf.py将在magic内部正确绑定名称。因此,下一次导入magic时,它将不会被重新加载(因为此函数已经将其放入sys.modules中),但它将正确地分配其所有子模块。

这似乎缓解了import机制中的一些问题,我在下面尝试对其进行诊断。


差异实际上来自于这样一个事实:在第一种情况下,模块的加载顺序与在第二种情况下不同。当您将print语句添加到每个模块时,您将在执行主脚本时看到以下内容:

加载魔术

正在加载imho

正在加载wtf

魔术内容:[‘__内建__’,‘__文档__’,‘__文件__’,‘__名称__’,‘__包__’,‘__路径__’]

对于第二种情况:

加载魔术

正在加载wtf

正在加载imho

魔术内容:[‘__内建__’,‘__文档__’,‘__文件__’,‘__名称__’,‘__包__’,‘__路径__’,‘imho’]

因此,在第一种情况下,magic和imho已经加载,并且import语句不重新加载已经加载的模块:

首先,如果模块已经存在于系统模块中(如果在导入机器外部调用加载程序,则有可能),则它将使用该模块进行初始化,而不是使用新模块

如果您像这样更改主脚本,例如:

if __name__ == '__main__':
    here = os.path.dirname(os.path.realpath(__file__))
    path = os.path.join(here, 'lib')
    sys.path.append(path)
    import magic.wtf

它与解释器的工作方式完全相同。

如果您修改wtf.py(出于演示目的),您的原始脚本也将按预期工作:

import sys

try:
    del sys.modules['magic.imho']
except:
    pass

import magic.imho

print "magic contents:", dir(magic)

或类似于:

import sys
import magic.imho
magic.imho = sys.modules['magic.imho']

print "magic contents:", dir(magic)

输出:

加载魔术

正在加载imho

正在加载wtf

正在加载imho

魔术内容:[‘__内建__’,‘__文档__’,‘__文件__’,‘__名称__’,‘__包__’,‘__路径__’,‘imho’]

注意:如果已加载且不同于导入,则sys.modules中存在模块。在第一种情况下,已加载,但未导入。因此它存在于sys.modules中,但不存在于当前命名空间中。所以我猜它是由您的加载器加载的,没有绑定imho(所以它只是magic模块)。然后,当Python看到import magic.imho时,它会检查magicmagic.imho是否已经加载(可以通过print sys.modules进行验证),并获取这个版本的magic并将其绑定到局部变量magic


相关:

  • Realoading Python modules

  • Traps for the Unwary in Python’s Import System(不应将本地包添加到sys.path)

  • Prevent Python from caching the imported modules

相关文章