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

Posted

技术标签:

【中文标题】在编译时提供函数元数据的大多数 Pythonic 方式?【英文标题】:Most Pythonic way to provide function metadata at compile time? 【发布时间】:2013-01-27 14:11:59 【问题描述】:

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

我看过几件事:

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

    跨模块装饰器: 装饰器会让 插件开发人员可以随意命名他的功能,并且简单 添加类似@Main.register(“dl”)的东西,但它们一定会 要求我修改另一个模块的命名空间并保持 Main 模块中的全局状态。我知道这很糟糕。

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

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

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

【问题讨论】:

只是检查你是否意识到这一点,但装饰器(和函数定义)在运行时执行,Python 在编译时唯一做的就是编译你的代码。 方法 2 没有任何问题。让他们做类似import plugin 和做@plugin.register('my_method_name')\ndef somefunc_meeting_an_api_spec():... 之类的事情 您可能想看看plac 是如何做到这一点的。 不会像cmd.Cmdreadline 选项卡扩展支持一起消除对短名称的担忧。 @sotapme:谢谢,这几乎正是我一直在尝试构建的那种东西!不幸的是,Cmd 似乎通过继承它们来注册方法,所以我认为这意味着我需要在运行时动态地对 Main 模块进行多重继承以使用不同的插件。 【参考方案1】:

在@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

【讨论】:

【参考方案2】:

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

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

【讨论】:

这正是我要找的东西! 我意识到这个问题有点老了,但是在这里查看我的相关问题***.com/questions/37192712/…。您是否遇到过使用存储在函数上的属性进行包装的问题?【参考方案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

【讨论】:

谢谢你;我喜欢这背后的设计解释。这种解释表明实现 get_commands() 列表的超类可能是一个好主意,因此开发人员必须尽可能少地做工作。 @mieubrisse:不需要任何课程。我已经添加了完整的插件示例。

以上是关于在编译时提供函数元数据的大多数 Pythonic 方式?的主要内容,如果未能解决你的问题,请参考以下文章

在 config.py 中提供全局配置变量的大多数 Pythonic 方式? [关闭]

Pythonic 按字段名称对命名元组列表进行排序的方法

将CSV文件数据读取为命名元组行的pythonic方法是啥?

将 CSV 值读入列表字典的大多数 Pythonic 方式

返回布尔值和消息的Pythonic方式[重复]

友元关系