我正在编写一个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)
该函数导入模块名,可能会使用给定的全局变量和局部变量来确定如何在包上下文中解释该名称。fromlist给出了应该从模块中导入的对象或子模块的名称。
来源: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()
这工作得很好,我只是想知道是否有一种更习惯的方式来完成我们用这段代码所做的事情。
注意,我特别不想使用卵或扩展点。这不是一个开源项目,我不期望有“插件”。这样做的目的是简化主要的应用程序代码,并且不需要每次添加新的命令模块时都修改它。
请参见:如何导入给定完整路径的模块?
对于2.7/3.1以上的Python,基本上就是这样做的。
有关更新版本,请参见importlib。import_module用于Python 2和Python 3。
或者使用__import__你可以导入一个模块列表,这样做:
>>> moduleNames = ['sys', 'os', 're', 'unittest']
>>> moduleNames
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, moduleNames)
直接摘自《潜入Python》。
注意:从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
类似于@monkut的解决方案,但可重用和容错描述在这里http://stamat.wordpress.com/dynamic-module-import-in-python/:
import os
import imp
def importFromURI(uri, absl):
mod = None
if not absl:
uri = os.path.normpath(os.path.join(os.path.dirname(__file__), uri))
path, fname = os.path.split(uri)
mname, ext = os.path.splitext(fname)
if os.path.exists(os.path.join(path,mname)+'.pyc'):
try:
return imp.load_compiled(mname, uri)
except:
pass
if os.path.exists(os.path.join(path,mname)+'.py'):
try:
return imp.load_source(mname, uri)
except:
pass
return mod
使用importlib
导入源文件
下面是一个稍微改编自文档的例子:
import sys
import importlib.util
file_path = 'pluginX.py'
module_name = 'pluginX'
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# Verify contents of the module:
print(dir(module))
从这里开始,module将是一个表示pluginX模块的模块对象(通过导入pluginX将同样的事情分配给pluginX)。因此,要调用pluginX中定义的hello函数(不带参数),请使用module.hello()。
为了从模块中获得效果“导入”功能,将其存储在加载模块的内存缓存中,然后执行相应的from import:
sys.modules[module_name] = module
from pluginX import hello
hello()
导入包
要导入一个包,调用import_module就足够了。假设在当前工作目录中有一个包文件夹pluginX;那就去做
import importlib
pkg = importlib.import_module('pluginX')
# check if it's all there..
print(dir(pkg))