2013-02-12 61 views
5

我正在用Python 2.7模块的形式构建一个非常基础的平台。该模块具有读取评估打印循环,其中输入的用户命令映射到函数调用。由于我正在试图为我的平台构建插件模块,所以函数调用将从我的主模块到任意插件模块。我希望一个插件生成器能够指定他想要触发他的函数的命令,所以我一直在寻找一种Pythonic方法来远程输入主模块中的command-> function dict中的映射插件模块。在编译时提供函数元数据的大多数Pythonic方法?

我已经看了几件事情:

  1. 方法名称解析:主模块将导入插件模块 和扫描它匹配某种格式,方法名。例如,对于 示例,它可能会将download_file_command(file)方法作为“下载文件” - > download_file_command添加到其 字典中。但是,获取简洁易用的命令名称(例如“dl”)要求功能的名称也较短,这不利于代码 的可读性。它还要求插件开发者符合 的精确命名格式。

  2. 跨模块的装饰:装饰会让 插件开发者的名字,无论他希望他的功能和简单的 添加类似@ Main.register(“DL”),但他们必然 要求我都修改另一个模块的名称空间,并在主模块中保持全局状态 。我知道这很糟糕。

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

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

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

+1

只是检查你意识到这一点,但装修(和函数定义)在运行时执行,Python做在编译时的唯一的事情就是编译代码。 – Duncan 2013-02-12 09:55:17

+1

方法2没有任何问题。让他们执行类似'import plugin'的操作并执行'@ plugin.register('my_method_name')\ ndef somefunc_meeting_an_api_spec():...' – 2013-02-12 09:57:08

+0

您可能想看看[plac](http://pypi.python.org/pypi/plac)就是这样做的。 – georg 2013-02-12 09:59:30

回答

4

用户定义的函数可以具有任意属性。所以你可以指定插件函数有一个具有特定名称的属性。例如:

那么,你的模块中,你可以建立这样的映射:

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

+0

这正是我寻找的东西的类型! – mieubrisse 2013-02-12 19:38:43

+0

我意识到这个问题有点旧,但在这里看到我的相关问题http://stackoverflow.com/questions/37192712/how-can-attributes-on-functions-survive-wrapping。您是否遇到使用存储在函数中的属性进行包装的问题? – matthewatabet 2016-05-12 16:38:10

1

有两个部分的插件系统:

  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

+0

谢谢你;我喜欢这个背后的设计解释。这个解释表明,实现get_commands()列表的超类可能是一个好主意,所以开发人员必须尽可能少地完成工作。 – mieubrisse 2013-02-12 19:47:44

+0

@mieubrisse:不需要课程。我已经添加了完整的插件示例。 – jfs 2013-02-12 21:16:42

4

建筑物或站在任意属性@ 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} 
相关问题