我正在以Python 2.7模块的形式构建一个非常基础的平台。该模块具有read-eval-print循环,其中输入的用户命令映射到函数调用。由于我试图为我的平台构建插件模块变得容易,因此函数调用将从我的主模块到任意插件模块。我想要一个插件构建器能够指定他想要触发其功能的命令,所以我一直在寻找一种Pythonic方法来远程输入主模块中命令 - >函数dict中的映射。插件模块。
我看了几件事:
方法名称解析:主模块将导入插件模块 并扫描它以查找与特定格式匹配的方法名称。对于 例如,它可能会将download_file_command(file)方法添加到其中 dict为“下载文件” - > download_file_command。但是,得到一个 简洁,易于键入的命令名称(例如,“dl”)要求 函数的名称也很短,这对代码不好 可读性。它还要求插件开发人员遵守 精确的命名格式。
跨模块装饰器:装饰器会让 插件开发人员无论他想要什么,简单地命名他的功能 添加类似@ Main.register(“dl”)的东西,但它们必然会 要求我修改另一个模块的命名空间并保持 主模块中的全局状态。我知道这非常糟糕。
相同模块装饰器:使用与上面相同的逻辑,我可以添加一个 装饰器,将函数的名称添加到某些命令名称>函数映射到本地 插件模块并使用一个检索到主模块的映射 API调用。这要求始终存在某些方法或 虽然继承了,并且 - 如果我对装饰器的理解是正确的 - 该函数只会在第一次运行时自行注册,并且每次都会不必要地重新注册 此后。
因此,我真正需要的是使用命令名称来触发它的Pythonic方法来注释函数,并且这种方式不能是函数的名称。我需要能够在导入模块时提取命令name->函数映射,而对插件开发人员的任何较少的工作都是一个很大的优势。
感谢您的帮助,如果我的Python理解存在任何缺陷,我向您道歉;我对这门语言比较陌生。
答案 0 :(得分:7)
构建或站在@ ericstalbot的答案的第一部分,您可能会发现使用如下装饰器很方便。
################################################################################
import functools
def register(command_name):
def wrapped(fn):
@functools.wraps(fn)
def wrapped_f(*args, **kwargs):
return fn(*args, **kwargs)
wrapped_f.__doc__ += "(command=%s)" % command_name
wrapped_f.command_name = command_name
return wrapped_f
return wrapped
################################################################################
@register('cp')
def copy_all_the_files(*args, **kwargs):
"""Copy many files."""
print "copy_all_the_files:", args, kwargs
################################################################################
print "Command Name: ", copy_all_the_files.command_name
print "Docstring : ", copy_all_the_files.__doc__
copy_all_the_files("a", "b", keep=True)
运行时输出:
Command Name: cp
Docstring : Copy many files.(command=cp)
copy_all_the_files: ('a', 'b') {'keep': True}
答案 1 :(得分:4)
用户定义的函数可以具有任意属性。因此,您可以指定插件函数具有具有特定名称的属性。例如:
def a():
return 1
a.command_name = 'get_one'
然后,在您的模块中,您可以构建如下映射:
import inspect #from standard library
import plugin
mapping = {}
for v in plugin.__dict__.itervalues():
if inspect.isfunction(v) and v.hasattr('command_name'):
mapping[v.command_name] = v
要阅读有关用户定义函数的任意属性,请参阅the docs
答案 2 :(得分:1)
插件系统中有两个部分:
您问题中提出的解决方案仅涉及第二部分。
根据您的要求,有很多方法可以实现这两种方法,例如,要启用插件,可以在应用程序的配置文件中指定它们:
plugins = some_package.plugin_for_your_app
another_plugin_module
# ...
实现插件模块的加载:
plugins = [importlib.import_module(name) for name in config.get("plugins")]
获取字典:command name -> function
:
commands = {name: func
for plugin in plugins
for name, func in plugin.get_commands().items()}
插件作者可以使用任何方法来实现get_commands()
,例如,使用前缀或装饰器 - 只要get_commands()
返回每个插件的命令字典,主应用程序就不应该关心。
例如,some_plugin.py
(完整来源):
def f(a, b):
return a + b
def get_commands():
return {"add": f, "multiply": lambda x,y: x*y}
它定义了两个命令add
,multiply
。