首页 > 解决方案 > python:从工作目录中稳健地导入模块

问题描述

为简单起见,我在 python 模块中为我的程序定义参数。然后使用 加载这些参数import。因此,我必须确保始终从工作目录加载,而不是其他任何地方(独立于执行脚本的位置或 python 路径中的可用模块)。

我找到了两个解决方案。首先修改路径:

import sys
from os import getcwd

import_path = sys.path
sys.path = [str(getcwd(), ]
import xxx
sys.path = import_path

或使用importlib

from pathlib import Path
from importlib.util import module_from_spec, spec_from_file_location

spec = spec_from_file_location('xxx', str(Path('.').expanduser()/'xxx.py'))
xxx = module_from_spec(spec)
spec.loader.exec_module(xxx)

当然,这可以分别包装到上下文管理器或函数中。

pythonic的方法是什么?这两种方法有优点和缺点吗?


我检查了如何导入位于当前工作目录中的 Python 库? 以及将python 包从本地目录导入解释器,但它们缺乏对健壮性的关注。

标签: pythonpython-3.x

解决方案


可以通过修改路径来实现本地导入。上下文管理器是一个合适的解决方案:

import sys
from pathlib import Path
from contextlib import contextmanager


@contextmanager
def local_import(dir_=None):
    """Only import modules within `dir_` (default: cwd)."""
    if dir_ is None:
        dir_ = Path.cwd()
    else:
        dir_ = Path(dir_).absolute().resolve(strict=True)
    import_path0 = sys.path[0]
    sys.path[0] = str(dir_)
    try:
        yield
    finally:
        sys.path[0] = import_path0

然后可以使用标准导入语法完成本地导入

with local_import():
    import xxx

此解决方案依赖于扫描路径的顺序,因此我们暂时替换sys.path[0]. 我们替换它,而不是预先避免与脚本目录的导入冲突。

笔记

您必须小心避免名称冲突,因为使用了 import 语句,具有相同名称的模块只会被导入一次。因此,如果工作目录和原始目录中存在同名的不同模块,则sys.path[0]只会导入其中一个。因此,local_import应该只用于只使用标准库或安装的第三方库的脚本,而不是用于从目录中导入其他脚本的脚本。对于不太可能的情况,您要导入具有相同名称的不同文件,可以使用以下功能:

import uuid
from importlib.util import module_from_spec, spec_from_file_location


def import_file(file, content=None):
    """Try importing `file` as module avoiding name clashes.

    If `content` is given `content = import_file('file.py', 'content')`
    roughly corresponds to `from file import content`
    else `file = import_file('file.py')`
    roughly corresponds to `import file`.

    Parameters
    ----------
    file : str or Path
        The Python file corresponding to the module.
    content : str, optional
        What to import from the module (optional).

    """
    file = Path(file).expanduser().resolve(strict=True)
    print(file)
    spec = spec_from_file_location(file.stem + str(uuid.uuid4()), str(file))
    module = module_from_spec(spec)
    spec.loader.exec_module(module)
    if content:
        print(module)
        return getattr(module, content)
    else:
        return module

推荐阅读