12

我正在以 Python 2.7 模块的形式构建一个非常基本的平台。这个模块有一个 read-eval-print 循环,输入的用户命令被映射到函数调用。因为我试图让为我的平台构建插件模块变得容易,所以函数调用将从我的主模块到任意插件模块。我希望插件构建器能够指定他想要触发他的函数的命令,所以我一直在寻找一种 Pythonic 方式来远程输入 Main 模块中的 command->function dict 中的映射。插件模块。

我看过几件事:

  1. 方法名解析:主模块将导入插件模块并扫描它以查找匹配特定格式的方法名。例如,它可能会将 download_file_command(file) 方法添加到其字典中作为“下载文件”-> download_file_command。但是,要获得一个简洁、易于键入的命令名称(例如,“dl”)要求函数名称也很短,这不利于代码的可读性。它还要求插件开发人员符合精确的命名格式。

  2. 跨模块装饰器:装饰器可以让插件开发人员随意命名他的函数,并简单地添加类似 @Main.register("dl") 的东西,但它们必然要求我修改另一个模块的命名空间并保持全局状态在主模块。我明白这是非常糟糕的。

  3. 同模块装饰器:使用与上面相同的逻辑,我可以添加一个装饰器,将函数名称添加到插件模块本地的某个命令名称->函数映射,并通过 API 调用检索到主模块的映射。这要求某些方法始终存在或继承,并且 - 如果我对装饰器的理解是正确的 - 该函数只会在第一次运行时注册自己,并且此后每次都不必要地重新注册自己。

因此,我真正需要的是一种 Pythonic 方式来使用应该触发它的命令名称来注释函数,并且这种方式不能是函数的名称。当我导入模块时,我需要能够提取命令名称-> 函数映射,并且插件开发人员方面的工作量减少是一大优势。

感谢您的帮助,如果我对 Python 的理解有任何缺陷,我深表歉意;我对这门语言比较陌生。

4

3 回答 3

12

在@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}
于 2013-02-13T10:28:52.363 回答
7

用户定义的函数可以具有任意属性。因此,您可以指定插件函数具有具有特定名称的属性。例如:

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

要了解用户定义函数的任意属性,请参阅文档

于 2013-02-12T12:36:38.207 回答
2

插件系统有两个部分:

  1. 发现插件
  2. 在插件中触发一些代码执行

您问题中提出的解决方案仅涉及第二部分。

根据您的要求,有很多方法可以实现这两种方法,例如,要启用插件,可以在应用程序的配置文件中指定它们:

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}

它定义了两个命令addmultiply.

于 2013-02-12T11:47:05.327 回答