在 python3 中使用 importlib 动态导入时出错
Posted
技术标签:
【中文标题】在 python3 中使用 importlib 动态导入时出错【英文标题】:Errors while dynamic imports using importlib in python3 【发布时间】:2018-10-21 09:02:48 【问题描述】:我一直在尝试将importlib
与 python3 (3.6) 一起使用。
目录结构
main.py
#Note: I will only modify line 4 that uses importlib
import importlib
if __name__ == '__main__':
print("In main.py")
hello = importlib.import_module('hello', package='./')
print("Loaded hello.py")
hello.hello()
hello.py
def hello():
print('Hello world')
文件夹/hello.py
def hello():
print('Hello world in folder')
观察
如果我这样做了
hello = importlib.import_module('hello', package='./')
或
hello = importlib.import_module('hello')
它从根文件夹导入 hello.py 并打印 hello world
。
如果我这样做了
hello = importlib.import_module('folder.hello')
它从根文件夹导入 folder/hello.py 并打印hello world in folder
。
但如果我这样做了
hello = importlib.import_module('hello', package='folder')
或
hello = importlib.import_module('hello', package='./folder')
报错
Traceback (most recent call last):
File "main.py", line 4, in <module>
hello = importlib.import_module('hello', package='./folder')
File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 953, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'hello'
问题
我不确定这里发生了什么。我很确定我对 python 模块和包的理解有问题。有人可以解释为什么这是预期的行为吗?
【问题讨论】:
【参考方案1】:如果第一个参数,要导入的模块是一个绝对模块引用(没有前导.
),则第二个参数被完全忽略。
要导入相对于另一个模块b
的模块a
,您必须使用
a = importlib.import_module('.a', package='b')
在你的情况下,这应该可以工作
hello = importlib.import_module('.hello', package='folder')
根据经验,如果您想使用 package
作为第二个参数,import package
应该可以工作。
from package import module
然后变成
importlib.import_module(module, package)
【讨论】:
importlib
超出了我的理解范围。我赞成您的回答,但现在 importlib.import_module('math.sin')
或 importlib.import_module('.sin', package='math')
不起作用。
@UmangGupta,那是因为sin
是数学模块中的一个函数,它本身不是一个模块。你需要做math . = importlib.import_module('math')
然后math.sin
将工作。名称是 import_module
而不是 import_function
@TarunLalwani 我认为这是有道理的。感谢您指出。仅供参考,这行得通importlib.import_module('random', package='numpy')
所以在实际问题中为什么hello = importlib.import_module('hello', package='folder')
这行不通?为什么我需要.hello
hello = importlib.import_module('hello', package='folder')
是给你一个错误还是它只是加载基本模块?
@TarunLalwani 如果我删除了.
文件夹中的hello.py
,它会给出No module named 'hello'
错误【参考方案2】:
@Mahesh 的回答是 100% 正确且准确的,但我想我们需要深入一层才能让您更好地理解它
下面是import_module
的代码
def import_module(name, package=None):
"""Import a module.
The 'package' argument is required when performing a relative import. It
specifies the package to use as the anchor point from which to resolve the
relative import to an absolute import.
"""
level = 0
if name.startswith('.'):
if not package:
msg = ("the 'package' argument is required to perform a relative "
"import for !r")
raise TypeError(msg.format(name))
for character in name:
if character != '.':
break
level += 1
return _bootstrap._gcd_import(name[level:], package, level)
您可以查看name
是否不以.
开头,然后if
部分不会被执行。你只有return _bootstrap._gcd_import(name[level:], package, level)
,它以level=0
作为值来执行
现在让我们进入那个函数,它有下面的代码
def _gcd_import(name, package=None, level=0):
"""Import and return the module based on its name, the package the call is
being made from, and the level adjustment.
This function represents the greatest common denominator of functionality
between import_module and __import__. This includes setting __package__ if
the loader did not.
"""
_sanity_check(name, package, level)
if level > 0:
name = _resolve_name(name, package, level)
return _find_and_load(name, _gcd_import)
同样,它只是执行_find_and_load(name, _gcd_import)
,现在因为level
是我们之前代码中的0
,package
参数根本没有被_find_and_load
方法传递或使用。现在您可以通过在下面运行来轻松验证这一点
import importlib
hello = importlib.import_module('hello', package='IAmNotAfolder')
hello.hello()
它将从基础hello.py
打印Hello World
如您所见,当名称不以.
开头时,根本不使用package
参数,这用于相对导入。这就是为什么您会收到错误 No module named 'hello'
的原因,因为它正在尝试从基本文件夹导入 hello.py
,而不管您在包中有什么。
希望这个答案能让你更容易理解幕后发生的事情
【讨论】:
1.是的,我刚刚意识到importlib.import_module('random', package='numpy')
从python 导入了random
模块,而不是numpy.random
。我现在对此感到很愚蠢。 2. 我猜find_and_load
没有在system.path
中搜索,而是只加载了包,对吗? 3. 感谢您发布代码。我同意 Mahesh 的观察,我对 numpy
和 sys
示例感到困惑:/
@UmangGupta,如果未加载,它正在加载包并返回对象。但它不会将其添加到您的本地/全局范围内。因此,如果您不将返回值放入变量中,则必须使用 sys.modules['hello'].hello()
之类的方式调用它
我不确定。如果我这样做linalg=importlib.import_module('scipy.linalg')
它可以工作,但linalg = importlib.import_module('.linalg', package='scipy')
这不会。这个import scipy; linalg = importlib.import_module('.linalg', package='scipy')
有效。
不确定你所说的矛盾是什么意思:-|
我认为这是版本问题.. 适用于 3.6 但不适用于 3.4。无论如何,非常感谢所有的帮助。以上是关于在 python3 中使用 importlib 动态导入时出错的主要内容,如果未能解决你的问题,请参考以下文章
使用 pip3:模块“importlib._bootstrap”没有属性“SourceFileLoader”
AttributeError:模块“importlib”没有属性“util”