使用自定义加载函数时,模块中未定义Python模块
问题描述
考虑使用以下程序加载目录中的所有模块:
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.imho
包imho
上没有导致以后引用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
(因为很多原因,注射是第一个),所以我使用了setattr
和rpartition
,如下所示:
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’]
因此,在第一种情况下,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
时,它会检查magic
和magic.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
相关文章