HOWTO · Python

在 Python 中从完整文件路径导入模块

本文将向你展示如何从路径导入 Python 文件。

本页内容

当插件、生成的文件或独立脚本不在常规包结构中时,按指定文件路径导入模块会很有用。对于一次性模块导入,importlib.util 会创建模块对象,其加载器会在该模块的命名空间中运行文件。执行文件前,先将模块注册到 sys.modules,这样初始化期间查询模块注册表的代码才能找到该模块。

使用 importlib.util 按路径导入 Python 文件

为文件指定一个有效的 Python 模块名称和准确路径。该名称是模块对象使用的标识符,不必与文件名相同,但应在你的应用中足够独特。示例会创建一个临时 Python 文件,因此无论当前工作目录是什么,都可以独立运行。

运行以下示例:

本节中的代码以及显示的标签或输出仍为英文。

from pathlib import Path
import importlib.util
import sys
import tempfile

with tempfile.TemporaryDirectory() as directory:
    module_path = Path(directory) / "greeting.py"
    module_path.write_text("def message():\n    return 'loaded from path'\n", encoding="utf-8")
    module_name = "greeting_from_path"
    spec = importlib.util.spec_from_file_location(module_name, module_path)
    module = importlib.util.module_from_spec(spec)
    sys.modules[module_name] = module
    spec.loader.exec_module(module)
    print(module.message())

最后一次调用会打印已加载文件中函数返回的值。spec_from_file_location() 会创建一个模块规格,描述模块名称和位置。module_from_spec() 根据该规格创建模块,而 exec_module() 则通过加载器执行模块源码。如果路径是动态提供的,使用此模式前请检查模块规格和加载器是否可用;文件缺失或加载器不支持时,无法用这种方式导入。

将文件所在目录添加到 sys.path

如果你希望通过常规导入方式从某个目录导入模块,请将文件所在目录添加到 sys.path,然后按模块名称导入。sys.path 是 Python 搜索导入内容时查找的位置列表。下面这个完整示例会在临时目录中创建模块,将该目录加入搜索路径,再按名称导入模块:

运行以下示例:

本节中的代码以及显示的标签或输出仍为英文。

from pathlib import Path
import importlib
import sys
import tempfile

with tempfile.TemporaryDirectory() as directory:
    module_name = f"greeting_{Path(directory).name}"
    module_path = Path(directory) / f"{module_name}.py"
    module_path.write_text("def message():\n    return 'loaded by name'\n", encoding="utf-8")
    sys.path.insert(0, directory)
    try:
        module = importlib.import_module(module_name)
        print(module.message())
    finally:
        sys.path.remove(directory)
        sys.modules.pop(module_name, None)

向搜索路径追加或插入目录,会改变当前进程后续的搜索行为,因此应尽量缩小路径更改的影响范围,并避免添加由不可信用户控制的目录。示例随后会移除该路径项和已缓存的模块,以限制影响。如果你有意使用常规导入语法,或需要从同一目录导入多个模块,这种方式会很方便。它仍遵循常规的名称解析和缓存规则:如果 sys.modules 中已存在该模块,就可能会直接复用;如果名称冲突,解析到的文件也可能与预期不同。

仅使用 exec() 运行可信的脚本文本

exec() 会在指定的命名空间中运行 Python 源码文本;它不会创建并注册常规模块对象。只有当任务是执行可信脚本内容时才使用它,不要用它代替模块导入。执行来自上传文件、请求或其他不可信来源的文本,可能会以当前进程的权限运行任意 Python 代码。单独的字典可以保存脚本创建的名称,但无法提供真实模块的完整导入行为或模块身份。

处理模块名称、包和导入错误

简单模块名称应使用有效标识符;如果要加载多个文件,请为每个模块选择稳定且独特的名称。重复使用同一名称会影响 sys.modules:在该名称下加载另一个文件可能会替换注册表条目,而之后的常规导入可能会返回已缓存的模块。请决定重复调用时应复用模块,还是有意重新加载。

直接按路径加载的文件不会自动成为其所属包的一部分。使用 from .helpers import value 等相对导入的代码依赖包上下文,而单文件规格本身不会重新创建这种上下文。对于包,最好通过包名称导入,并确保父目录位于 sys.path 中。加载失败时,请检查路径是否指向可读取的 Python 文件、模块规格是否已创建,以及加载器是否存在;然后查看模块执行时的回溯,找出根本原因。导入时运行的代码本身也可能失败,因此路径有效并不保证初始化一定成功。

选择合适的方法

如果需要模块对象,并且只需从指定文件路径导入一次,请使用 importlib.util。如果某个目录应参与常规导入查找,尤其是要导入其中多个相关模块,请将该目录添加到 sys.path。只有在你要把可信源码文本作为脚本运行、且不需要模块语义时,才选择 exec()。对于可复用的项目代码,使用常规包和常规导入通常更便于管理依赖、命名和缓存。