如何根据字符串名称动态导入模块?
我正在写一个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
的模块,可能会使用给定的globals
和locals
来确定如何在包的上下文中解释这个名字。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 个回答
注意:从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
在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')
如果你使用的是Python 2.7或3.1之前的版本,基本上就是这样做的。
对于更新的版本,可以查看 importlib.import_module
,具体内容可以参考 Python 2 和 Python 3 的文档。
或者,你也可以使用 __import__
来导入一系列模块,方法如下:
>>> moduleNames = ['sys', 'os', 're', 'unittest']
>>> moduleNames
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, moduleNames)
这段内容直接摘自 Dive Into Python。