在编译时提供函数元数据的大多数 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.Cmd
与readline
选项卡扩展支持一起消除对短名称的担忧。
@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
它定义了两个命令add
,multiply
。
【讨论】:
谢谢你;我喜欢这背后的设计解释。这种解释表明实现 get_commands() 列表的超类可能是一个好主意,因此开发人员必须尽可能少地做工作。 @mieubrisse:不需要任何课程。我已经添加了完整的插件示例。以上是关于在编译时提供函数元数据的大多数 Pythonic 方式?的主要内容,如果未能解决你的问题,请参考以下文章
在 config.py 中提供全局配置变量的大多数 Pythonic 方式? [关闭]