我正在编写一个Python应用程序,它以命令作为参数,例如:
1 | $ python myapp.py command1 |
我希望应用程序是可扩展的,也就是说,能够添加实现新命令的新模块,而不需要更改主应用程序源代码。这棵树看起来像:
1 2 3 4 5 6 7 8 | 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)
The function imports the module name, potentially using the given globals and locals to determine how to interpret the name in a package context. The fromlist gives the names of objects or submodules that should be imported from the module given by name.
Source: https://docs.python.org/3/library/functions.html#import
所以目前我有这样的东西:
1 2 3 4 5 6 7 | command = sys.argv[1] try: command_module = __import__("myapp.commands.%s" % command, fromlist=["myapp.commands"]) except ImportError: # Display error message command_module.run() |
这工作得很好,我只是想知道是否有一种更习惯的方法来完成我们用这段代码所做的事情。
注意,我特别不想使用egg或扩展点。这不是一个开源项目,我也不期望有"插件"。重点是简化主应用程序代码,并消除每次添加新命令模块时修改它的需要。
对于比2.7/3.1更老的Python,基本上就是这样做的。
对于较新的版本,Python 2和Python 3请参见
如果需要,也可以使用
或者使用
1 2 3 4 | >>> moduleNames = ['sys', 'os', 're', 'unittest'] >>> moduleNames ['sys', 'os', 're', 'unittest'] >>> modules = map(__import__, moduleNames) |
直接从一头扎进巨蟒。
Python 2.7和3.1及更高版本的推荐方法是使用
importlib.import_module(name, package=None)
Import a module. The name argument specifies what module to import in absolute or relative terms (e.g. either pkg.mod or ..mod). If the name is specified in relative terms, then the package argument must be set to the name of the package which is to act as the anchor for resolving the package name (e.g. import_module('..mod', 'pkg.subpkg') will import pkg.mod).
如。
1 | my_module = importlib.import_module('os.path') |
Note: imp is deprecated since Python 3.4 in favor of importlib
如前所述,imp模块提供了加载功能:
1 2 | imp.load_source(name, path) imp.load_compiled(name, path) |
我以前也用过类似的方法。
在我的例子中,我定义了一个特定的类,并定义了所需的方法。一旦我加载模块,我将检查类是否在模块中,然后创建一个类的实例,像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 |
使用imp模块,或者更直接的
如果你想让你的当地人也这样:
1 2 3 4 | >>> mod = 'sys' >>> locals()['my_module'] = __import__(mod) >>> my_module.version '2.6.6 (r266:84297, Aug 24 2010, 18:46:32) [MSC v.1500 32 bit (Intel)]' |
您可以使用
1 | exec"import myapp.commands.%s" % command |
类似于@monkut的解决方案,但可重用和容错在这里描述http://stamat.wordpress.com/dynamic-module-import-in-python/:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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。
导入源文件文档实际上提供了一个食谱,它是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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) # check if it's all there.. def bla(mod): print(dir(mod)) bla(module) |
导入包
在当前目录下导入包(例如
1 2 3 4 5 6 7 8 | import importlib pluginX = importlib.import_module('pluginX') # check if it's all there.. def bla(mod): print(dir(mod)) bla(module) |
例如:我的模块名是jan_module/ feb_module/ mar_module
month='feb'
exec 'from %s_module import *'%(month)
听起来你真正想要的是插件架构。
您应该了解setuptools包提供的入口点功能。它提供了一个发现为您的应用程序加载的插件的好方法。
下面这篇文章对我很有用:
1 2 3 4 | >>>import imp; >>>fp, pathname, description = imp.find_module("/home/test_module"); >>>test_module = imp.load_module("test_module", fp, pathname, description); >>>print test_module.print_hello(); |
如果你想导入shell脚本:
1 | python -c '' |
下面这些对我很有用:
1 2 3 4 5 6 7 8 9 10 | import sys, glob sys.path.append('/home/marc/python/importtest/modus') fl = glob.glob('modus/*.py') modulist = [] adapters=[] for i in range(len(fl)): fl[i] = fl[i].split('/')[1] fl[i] = fl[i][0:(len(fl[i])-3)] modulist.append(getattr(__import__(fl[i]),fl[i])) adapters.append(modulist[i]()) |
它从modus文件夹加载模块。模块有一个与模块名称相同的类。例如modus/ module .py文件包含:
1 2 3 4 | class modu1(): def __init__(self): self.x=1 print self.x |
结果是一个动态加载类"适配器"列表。