大多数Pythonic方式在编译时提供函数元数据?

时间:2013-02-12 09:52:38

标签: python python-decorators

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

我看了几件事:

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

  2. 跨模块装饰器:装饰器会让 插件开发人员无论他想要什么,简单地命名他的功能 添加类似@ Main.register(“dl”)的东西,但它们必然会 要求我修改另一个模块的命名空间并保持 主模块中的全局状态。我知道这非常糟糕。

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

  4. 因此,我真正需要的是使用命令名称来触发它的Pythonic方法来注释函数,并且这种方式不能是函数的名称。我需要能够在导入模块时提取命令name->函数映射,而对插件开发人员的任何较少的工作都是一个很大的优势。

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

3 个答案:

答案 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)

插件系统中有两个部分:

  1. 发现插件
  2. 在插件中触发一些代码执行
  3. 您问题中提出的解决方案仅涉及第二部分。

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

    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