在 Python 中,要导入模块,可以在模块的开始位置使用 import,但是预先不知道需要导入什么模块时,就不能导入模块了。在这种情况下,我们需要在 Python 中动态导入模块、类、函数。动态导入模块也能满足让我们在运行时根据需要加载模块,而不是在程序开始时就进行导入。

什么是动态导入

动态导入是指在程序运行过程中根据需要加载模块、类、函数,动态导入只需提供以点分隔的模块路径的字符串。与静态导入不同,动态导入可以根据程序的状态、条件和用户输入来决定导入哪些模块,使程序更加灵活。

Python 动态导入模块(包)

Python 有多种实现实现动态导入的方法,其中最简单的一种是使用 importlib 库中的 import_module() 函数;也可以使用 importlib 库中其他函数或类。

使用 importlib.import_module 动态导入模块

如下是一个在 Python 中使用 importlib.import_module() 函数动态导入模块的示例,该函数的参数为 String:

module = importlib.import_module("django.db.utils");

使用文件路径动态导入模块

import importlib

def import_module_by_location(module_name, module_location):
    """
    导入使用点分隔的路径对应的模块
    """
    spec = importlib.util.spec_from_file_location(module_name, module_location)
    module = importlib.util.module_from_spec(spec)
    return module

# 使用定义的函数动态导入模块
module_location = "/path/to/module.py"
module_name = "mymodule"
module = import_module_by_path(module_name, module_location)

# 调用动态导入的模块的函数、类
module.fun()
module.MyClass()

解释说明:

  • 使用 importlib.util.spec_from_file_location() 函数根据给定的路径创建一个 ModuleSpec 对象
  • 使用 importlib.util.module_from_spec() 函数根据 ModuleSpec 对象导入模块

Python 动态导入类、函数

在 Python 中没有直接定义动态导入类、函数的方法,需要自定义动态导入类、函数的方法。

自定义动态导入类、函数的方法

如下给出定义的方法,你只需把代码复制到你的文件中即可使用:

import sys
from importlib import import_module

# 从 django 中复制
def import_from_string(dotted_path):
    """
    导入使用点分隔的模块中的类或函数,最后的部分为类名或函数名,加载错误将抛出 ImportError
    """
    try:
        module_path, class_name = dotted_path.rsplit(".", 1)
    except ValueError as err:
        raise ImportError("%s 看起来不像是模块路径" % dotted_path) from err

    try:
        # 检查模块是否已经被加载并初始化.
        if not (
            (module := sys.modules.get(module_path))
            and (spec := getattr(module, "__spec__", None))
            and getattr(spec, "_initializing", False) is False
        ):
            module = import_module(module_path)
        return getattr(module, class_name)
    except AttributeError as err:
        raise ImportError(
            '模块 "%s" 未定义 "%s" 属性/函数/类'
            % (module_path, class_name)
        ) from err

解释说明:

  • 首先根据提供的以点分隔的路径生成模块名和类名或函数名
  • 检查模块是否已经被加载并初始化,如果已经加载了,无需重新加载

使用自定义函数动态导入类、函数

Python 中动态导入类和函数是一样的,因为他们可以看作是模块的属性。

如下给出一个使用自定义函数动态导入类、函数的示例:

a_fun = import_from_string("subprocess.run")
a_fun(["ls", "-l"], text=True)

a_cls = import_from_string("subprocess.Popen")
po = a_cls()

注意

在 Python 中动态导入模块可能会增加代码的可维护性和可扩展性,但也会带来一些安全风险。例如,如果用户可以输入需要导入的模块的完整路径,可能会执行恶意操作。因此在 Python 中使用动态导入时,应该谨慎处理用户输入,以防止安全漏洞。

结语

在本文中我们介绍了在 Python 中动态导入模块的两种方法,并给出了最简单的动态导入模块的方法,同时给出了动态导入类、函数的自定义函数,使用自定义函数,可以非常方便的动态导入类和函数。