如何根据字符串名称动态导入模块?

698 投票
10 回答
471725 浏览
提问于 2025-04-11 19:15

我正在写一个Python应用程序,它可以接收一个命令作为参数,比如:

$ python myapp.py command1

我希望这个应用程序是可扩展的,也就是说,我想添加新的模块来实现新的命令,而不需要修改主应用程序的源代码。它的结构大概是这样的:

myapp/
    __init__.py
    commands/
        __init__.py
        command1.py
        command2.py
    foo.py
    bar.py

所以我希望应用程序在运行时能找到可用的命令模块,并执行相应的模块。

Python定义了一个__import__()函数,它接受一个字符串作为模块名:

__import__(name, globals=None, locals=None, fromlist=(), level=0)

这个函数会导入名为name的模块,可能会使用给定的globalslocals来确定如何在包的上下文中解释这个名字。fromlist提供了应该从name指定的模块中导入的对象或子模块的名称。

来源: https://docs.python.org/3/library/functions.html#__import_

所以目前我有这样的代码:

command = sys.argv[1]
try:
    command_module = __import__("myapp.commands.%s" % command, fromlist=["myapp.commands"])
except ImportError:
    # Display error message

command_module.run()

这个方法运行得很好,我只是想知道是否有更符合Python习惯的方式来实现我们正在做的事情。

需要注意的是,我特别不想使用“egg”或扩展点。这不是一个开源项目,我也不指望会有“插件”。我的目的是简化主应用程序的代码,并且在每次添加新的命令模块时,不需要修改它。


另见: 如何根据完整路径导入模块?

10 个回答

137

注意:从Python 3.4开始,imp模块已经不推荐使用,建议使用importlib

正如提到的,imp模块提供了一些加载功能:

imp.load_source(name, path)
imp.load_compiled(name, path)

我之前用过这些功能来做类似的事情。

在我的例子中,我定义了一个特定的类,并且这个类有一些必要的方法。加载模块后,我会检查这个类是否在模块里,然后创建这个类的一个实例,大概是这样的:

import imp
import os

def load_from_file(filepath):
    class_inst = None
    expected_class = 'MyClass'

    mod_name,file_ext = os.path.splitext(os.path.split(filepath)[-1])

    if file_ext.lower() == '.py':
        py_mod = imp.load_source(mod_name, filepath)

    elif file_ext.lower() == '.pyc':
        py_mod = imp.load_compiled(mod_name, filepath)

    if hasattr(py_mod, expected_class):
        class_inst = getattr(py_mod, expected_class)()

    return class_inst
403

在Python 2.7和3.1及以后的版本中,推荐的方式是使用importlib模块来导入模块:

importlib.import_module(name, package=None)

这个函数用来导入一个模块。这里的name参数是用来指定你想导入的模块名称,可以是绝对路径或者相对路径(比如可以写成pkg.mod或者..mod)。如果你用的是相对路径,那么package参数就必须指定一个包的名称,这个包会作为参考来解析模块的名称(例如,import_module('..mod', 'pkg.subpkg')会导入pkg.mod)。

例如:

my_module = importlib.import_module('os.path')
408

如果你使用的是Python 2.7或3.1之前的版本,基本上就是这样做的。

对于更新的版本,可以查看 importlib.import_module,具体内容可以参考 Python 2Python 3 的文档。

或者,你也可以使用 __import__ 来导入一系列模块,方法如下:

>>> moduleNames = ['sys', 'os', 're', 'unittest'] 
>>> moduleNames
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, moduleNames)

这段内容直接摘自 Dive Into Python

撰写回答