简体   繁体   English

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

[英]Most Pythonic way to provide function metadata at compile time?

I am building a very basic platform in the form of a Python 2.7 module.我正在以 Python 2.7 模块的形式构建一个非常基本的平台。 This module has a read-eval-print loop where entered user commands are mapped to function calls.该模块有一个 read-eval-print 循环,其中输入的用户命令被映射到函数调用。 Since I am trying to make it easy to build plugin modules for my platform, the function calls will be from my Main module to an arbitrary plugin module.由于我试图为我的平台轻松构建插件模块,因此函数调用将从我的 Main 模块到任意插件模块。 I'd like a plugin builder to be able to specify the command that he wants to trigger his function, so I've been looking for a Pythonic way to remotely enter a mapping in the command->function dict in the Main module from the plugin module.我希望插件构建器能够指定他想要触发他的函数的命令,所以我一直在寻找一种 Pythonic 方式来远程输入映射到主模块的命令->函数字典中插件模块。

I've looked at several things:我看了几件事:

  1. Method name parsing: the Main module would import the plugin module and scan it for method names that match a certain format.方法名称解析: Main 模块将导入插件模块并扫描它以查找匹配特定格式的方法名称。 For example, it might add the download_file_command(file) method to its dict as "download file" -> download_file_command.例如,它可能会将 download_file_command(file) 方法添加到其字典中作为“下载文件”-> 下载文件命令。 However, getting a concise, easy-to-type command name (say, "dl") requires that the function's name also be short, which isn't good for code readability.但是,获得简洁、易于键入的命令名称(例如“dl”)要求函数的名称也很短,这不利于代码的可读性。 It also requires the plugin developer to conform to a precise naming format.它还要求插件开发人员符合精确的命名格式。

  2. Cross-module decorators: decorators would let the plugin developer name his function whatever he wants and simply add something like @Main.register("dl"), but they would necessarily require that I both modify another module's namespace and keep global state in the Main module.跨模块装饰器:装饰器可以让插件开发者随意命名他的函数,并简单地添加类似@Main.register("dl") 的东西,但它们必然要求我同时修改另一个模块的命名空间并保持全局状态主模块。 I understand this is very bad.我明白这是非常糟糕的。

  3. Same-module decorators: using the same logic as above, I could add a decorator that adds the function's name to some command name->function mapping local to the plugin module and retrieve the mapping to the Main module with an API call.同模块装饰器:使用与上述相同的逻辑,我可以添加一个装饰器,将函数的名称添加到插件模块本地的某个命令名称->函数映射中,并通过 API 调用检索到 Main 模块的映射。 This requires that certain methods always be present or inherited though, and - if my understanding of decorators is correct - the function will only register itself the first time it is run and will unnecessarily re-register itself every subsequent time thereafter.这要求某些方法始终存在或继承,并且 - 如果我对装饰器的理解是正确的 - 该函数只会在第一次运行时注册自己,并且此后每次都不必要地重新注册自己。

Thus, what I really need is a Pythonic way to annotate a function with the command name that should trigger it, and that way can't be the function's name.因此,我真正需要的是一种 Pythonic 的方式来用应该触发它的命令名称来注释一个函数,并且这种方式不能是函数的名称。 I need to be able to extract the command name->function mapping when I import the module, and any less work on the plugin developer's side is a big plus.当我导入模块时,我需要能够提取命令名称-> 函数映射,插件开发人员方面的工作量减少是一个很大的优势。

Thanks for the help, and my apologies if there are any flaws in my Python understanding;感谢您的帮助,如果我对 Python 的理解有任何缺陷,我深表歉意; I'm relatively new to the language.我对这门语言比较陌生。

Building or Standing on the first part of @ericstalbot's answer, you might find it convenient to use a decorator like the following.在@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)

Output when run:运行时输出:

Command Name:  cp
Docstring   :  Copy many files.(command=cp)
copy_all_the_files: ('a', 'b') {'keep': True}

User-defined functions can have arbitrary attributes.用户定义的函数可以具有任意属性。 So you could specify that plug-in functions have an attribute with a certain name.因此,您可以指定插件函数具有特定名称的属性。 For example:例如:

def a():
  return 1

a.command_name = 'get_one'

Then, in your module you could build a mapping like this:然后,在您的模块中,您可以构建这样的映射:

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

To read about arbitrary attributes for user-defined functions see the docs要了解用户定义函数的任意属性,请参阅文档

There are two parts in a plugin system:插件系统有两个部分:

  1. Discover plugins发现插件
  2. Trigger some code execution in a plugin在插件中触发一些代码执行

The proposed solutions in your question address only the second part.您问题中提出的解决方案仅针对第二部分。

There many ways to implement both depending on your requirements eg, to enable plugins, they could be specified in a configuration file for your application:根据您的要求,有很多方法可以实现这两者,例如,要启用插件,可以在应用程序的配置文件中指定它们:

plugins = some_package.plugin_for_your_app
    another_plugin_module
    # ...

To implement loading of the plugin modules:要实现插件模块的加载:

plugins = [importlib.import_module(name) for name in config.get("plugins")]

To get a dictionary: command name -> function :获取字典: command name -> function

commands = {name: func 
            for plugin in plugins
            for name, func in plugin.get_commands().items()}

Plugin author can use any method to implement get_commands() eg, using prefixes or decorators — your main application shouldn't care as long as get_commands() returns the command dictionary for each plugin.插件作者可以使用任何方法来实现get_commands()例如,使用前缀或装饰器 — 只要get_commands()返回每个插件的命令字典,您的主应用程序就不必关心。

For example, some_plugin.py (full source):例如, some_plugin.py (完整源代码):

def f(a, b):
    return a + b

def get_commands():
    return {"add": f, "multiply": lambda x,y: x*y}

It defines two commands add , multiply .它定义了两个命令addmultiply

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM