我正在编写一个将命令作为参数的 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 给出了应该从 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()
这工作得很好,我只是想知道是否可能有一种更惯用的方式来完成我们用这段代码所做的事情。
请注意,我特别不想使用鸡蛋或扩展点。这不是一个开源项目,我不希望有“插件”。重点是简化主应用程序代码,并且无需在每次添加新命令模块时对其进行修改。
dir(__import__)
。 fromlist 应该是模拟“从名称导入...”的名称列表。
importlib
:stackoverflow.com/a/54956419/687896
对于早于 2.7/3.1 的 Python,这几乎就是您的操作方式。
对于较新的版本,请参阅 Python 2 和 Python 3 的 importlib.import_module
。
如果您愿意,也可以使用 exec
。
或者使用 __import__
,您可以通过执行以下操作导入模块列表:
>>> moduleNames = ['sys', 'os', 're', 'unittest']
>>> moduleNames
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, moduleNames)
直接从 Dive Into Python 撕下。
Python 2.7 和 3.1 及更高版本的 recommended 方式是使用 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')
__import__
函数来支持上述模块。
os.path
; from os.path import *
怎么样?
globals().update(my_module.__dict)
注意:imp 自 Python 3.4 起已弃用,取而代之的是 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
expected_class
中,因此您可以改为使用 class_inst = getattr(py_mod,expected_name)()
。
现在您应该使用 importlib。
导入源文件
docs 实际上为此提供了一个方法,它类似于:
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)
通过这种方式,您可以从模块 pluginX.py
访问成员(例如,函数“hello
”)——在此代码段中称为 module
——在其命名空间下;例如,module.hello()
。
如果要导入成员(例如,“hello
”),您可以在内存中的模块列表中包含 module
/pluginX
:
sys.modules[module_name] = module
from pluginX import hello
hello()
导入一个包
在当前目录下导入包(eg、pluginX/__init__.py
)实际上很简单:
import importlib
pkg = importlib.import_module('pluginX')
# check if it's all there..
def bla(mod):
print(dir(mod))
bla(pkg)
import module_name
,请将 sys.modules[module_name] = module
添加到代码末尾
如果你想在当地人那里得到它:
>>> 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)]'
同样适用于 globals()
locals()
函数的 documentation 明确警告“不应修改此字典的内容”。
您可以使用 exec
:
exec("import myapp.commands.%s" % command)
as command_module
添加到 import 语句的末尾,然后执行 command_module.run()
与@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
下面的作品对我有用:
>>>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 脚本中导入:
python -c '<above entire code in one line>'
以下对我有用:
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/modu1.py 包含:
class modu1():
def __init__(self):
self.x=1
print self.x
结果是动态加载的类“适配器”列表。
fl
是什么? for 循环定义过于复杂。路径是硬编码的(例如,不相关)。它正在使用不鼓励的 python (__import__
),所有这些 fl[i]
的东西是干什么用的?这基本上是不可读的,并且对于不那么难的事情来说是不必要的复杂 - 请参阅其单线投票的最高投票答案。
不定期副业成功案例分享
__import__
。 2.7 文档:“因为这个函数是供 Python 解释器使用的,而不是一般用途,所以最好使用 importlib.import_module()...”当使用 python3 时,imp
模块解决了这个问题,如蒙库特下面提到。for element in
和map()
时还需要foreach
:)