使用“当前目录”导入模块

5

我有以下代码来动态加载一个模块:

def load_module(absolute_path):
    import importlib.util
    module_name, _ = os.path.splitext(os.path.split(absolute_path)[-1])
    try:
        py_mod = imp.load_source(module_name, absolute_path)
    except ImportError:
        module_root = os.path.dirname(absolute_path)
        print("Could not directly load module, including dir: {}".format(module_root))
        spec = importlib.util.spec_from_file_location(
            module_name, absolute_path, submodule_search_locations=[module_root, "."])
        py_mod = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(py_mod)
    return py_mod

它很好用,但当它尝试导入同一文件夹中的脚本时(而不是与相同名称的包相关),会遇到问题。例如,脚本a.py正在执行import b。这将导致错误ImportError: No module named 'b'(在Python 3中很常见)。

但我真的想找到解决方法?可以通过添加以下内容来解决:

import sys
sys.path.append(".")

请编写“a”脚本。

虽然我希望它可以通过以下方式解决:

submodule_search_locations=[module_root, "."]

噢,是的,我的理由是我也想支持导入不是适当的包/模块,而只是一些在解释器中工作的脚本。

可重现的代码:

~/example/a.py

import b

~/example/b.py

print("hi")

~/somewhere_else/main.py(位于a/b不同的位置)

import sys, os, imp, importlib.util

def load_module(absolute_path) ...

load_module(sys.argv[1])

然后在命令行上运行:

cd ~/somewhere_else
python3.5 main.py /home/me/example/a.py

导致出现ImportError: No module named 'b'的原因是...

以下代码可以解决此问题,但我们不可能在所有脚本中手动添加sys.path

~/example/a.py (2)

import sys
sys.path.append(".")
import b

我真的希望其他人可能有我没有想到的解决方案。

附录

def load_module(absolute_path):
    import importlib.util
    module_name, _ = os.path.splitext(os.path.split(absolute_path)[-1])
    try:
        py_mod = imp.load_source(module_name, absolute_path)
    except ImportError as e:
        if "No module named" not in e.msg:
            raise e

        missing_module = e.name
        module_root = os.path.dirname(absolute_path)

        if missing_module + ".py" not in os.listdir(module_root):
            msg = "Could not find '{}' in '{}'"
            raise ImportError(msg.format(missing_module, module_root))

        print("Could not directly load module, including dir: {}".format(module_root))
        sys.path.append(module_root)
        spec = importlib.util.spec_from_file_location(module_name, absolute_path)
        py_mod = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(py_mod)
    return py_mod

请不要删帖后重新发布以逃避您收到的投票。如果您想从同一目录导入但不想使用包,是的,您需要操作 sys.path - Martijn Pieters
@MartijnPieters,由于我对它进行了大量的改进,所以不得不等待将其取消挂起有点傻。 - PascalVKooten
这就是审核队列的作用。像那样删除和重新发布问题会导致你被自动禁言。 - Martijn Pieters
2个回答

5
这里使用动态导入并不重要;任何假设当前目录在路径上的代码都会遇到同样的问题。这些脚本有责任确保当前目录自己在路径上。
不要使用'.'(当前工作目录),而是使用__file__全局变量将目录添加到路径中:
import os.path
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)

你可以在出现ImportError时,通过将os.path.dirname(absolute_path)添加到sys.path来重试动态导入(也许是检测到短暂导入失败),但这是一个巨大的飞跃,因为你无法区分缺少依赖项和模块对sys.path的假设。

也许作为一种安全措施,可以扫描该文件夹(在我的脚本中称为module_root)是否包含一个不存在的名称文件(如您的链接所指出的)。这应该是非常安全的;否则脚本无论如何都不会运行。我没有意识到在执行该命令之前只需添加sys.path.append(module_root)(但仅当该名称的文件存在时)。所以非常感谢,它确实澄清了问题。它还回答了我为什么能够在解释器中进行此类导入的问题:Emacs将当前目录添加到路径中。 - PascalVKooten
当运行脚本时,定义脚本的目录也会自动添加到 sys.path 中,但这不是我依赖的东西。 - Martijn Pieters
是的,但我希望它从命令行运行,因此从不同的目录运行。我进行了仔细的修补,见附录。 - PascalVKooten

0
在我的情况下,有效的方法是使用importlib.import_module('file_basename'),没有指定任何包。请参考以下尝试、错误和成功记录:
>>> importlib.import_module('.trainer', '.')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 961, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 973, in _find_and_load_unlocked
ModuleNotFoundError: No module named '.'

>>> importlib.import_module('.trainer', None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/importlib/__init__.py", line 122, in import_module
    raise TypeError(msg.format(name))
TypeError: the 'package' argument is required to perform a relative import for '.trainer'

>>> importlib.import_module('.trainer', '')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/importlib/__init__.py", line 122, in import_module
    raise TypeError(msg.format(name))
TypeError: the 'package' argument is required to perform a relative import for '.trainer'

>>> importlib.import_module('trainer')
<module 'trainer' from '/home/ubuntu/project1/trainer.py'>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接