可插入的Python子命令模式?

Joh*_*nst 2 python python-3.x

我正在寻找一个关于如何实现Python子命令的好模式,其中主命令在运行时查找子命令(而不是知道所有可能的子命令的列表;这使得“应用程序”可以轻松地使用新的子命令进行扩展,而无需更改主代码)

例如:

 topcmd.py foo
Run Code Online (Sandbox Code Playgroud)

将查找/some/dirfoo.py如果存在,则运行它。或者它的一些变体。

调用的代码foo.py最好是类或对象上定义良好的函数或方法。

met*_*ter 6

虽然这个问题实际上相当广泛,但在典型的默认 Python 安装(即使用 )中有足够的可用工具,setuptools这是相对可以实现的,以实际上可扩展的方式,以便可以以提供的方式创建/安装其他包主程序的新的、可发现的子命令。

您的基础包可以提供一个标准的entry_point,其形式是console_scripts指向您的入口点,它将所有参数输入到某个参数解析器的实例中(例如argparse),以及您可以在类似方案下实现的某种注册表console_scripts,除了在您的特定entry_points组下,以便它会迭代每个条目并实例化对象,这些对象也将提供自己的实例,ArgumentParser您的主入口点将动态地将其注册为子命令,从而向您的用户显示哪些子命令实际可用以及哪些子命令他们的调用可能是这样的。

举个例子,在你的主包中setup.py,你可能有一个像这样的条目

setup(
    name='my.package',
    # ...
    entry_points={
        'console_scripts': [
            'topcmd = my.package.runtime:main',
        ],
        'my.package.subcmd': [
            'subcmd1 = my.package.commands:subprog1',
            'subcmd2 = my.package.commands:subprog2',
        ],
    },
    # ...
)
Run Code Online (Sandbox Code Playgroud)

在源文件内部my/package/runtime.pymain方法必须构造一个新的 ArgumentParser 实例,并在迭代 提供的入口点时pkg_resources.working_set,例如:

from pkg_resources import working_set

def init_parser(argparser):  # pass in the argparser provided by main
    commands = argparser.add_subparsers(dest='command')
    for entry_point in working_set.iter_entry_points('my.package.subcmd'):
        subparser = commands.add_parser(entry_point.name)
        # load can raise exception due to missing imports or error in object creation
        subcommand = entry_point.load()
        subcommand.init_parser(subparser) 
Run Code Online (Sandbox Code Playgroud)

因此,在该main函数中,它创建的 argparser 实例可以传递到上面的函数中,并且入口点将'subcmd1 = my.package.commands:subprog1'被加载。在内部my/package/command.pyinit_parser必须提供一个实现的方法,它将采用提供的子解析器并用所需的参数填充它:

class SubProgram1(object):
    def init_parser(self, argparser)
        argparser.add_argument(...) 

subprog1 = SubProgram1() 
Run Code Online (Sandbox Code Playgroud)

哦,最后一件事,在将参数传递给 main 后argparser.parse_args(...),命令的名称被提供给argparser.command. 应该可以将其更改为实际实例,但这可能会或可能不会实现您真正想要的(因为主程序可能希望在实际使用命令之前进行进一步的工作/验证)。该部分是另一个复杂的部分,但至少参数解析器应该包含实际运行正确的子程序所需的信息。

当然,这绝对不包括错误检查,并且必须以某种形式实现,以防止错误的子命令类破坏主程序。我使用了像这样的模式(尽管实现要复杂得多),它可以支持任意数量的嵌套子命令。此外,想要实现自定义命令的包可以简单地将自己的条目添加到自己的 入口点组(在本例中my.package.subcmd为 to )setup.py。例如:

setup(
    name="some.other.package",
    # ...
    entry_points={
        'my.package.subcmd': [
            'extracmd = some.other.package.commands:extracmd',
        ],
    },
    # ...
)
Run Code Online (Sandbox Code Playgroud)

附录:

根据要求,生产中使用的实际实现位于我当前维护的包( calmjs )中。安装该包(到 virtualenv 中)并在命令行上运行应该显示与主包的入口点calmjs中定义的条目相同的子命令列表。安装扩展功能的附加包(例如calamjs.webpack)并再次运行现在将作为附加子命令列出。calmjscalmjs.webpack

入口点将子类的实例引用到该类Runtime,其中有一个地方添加了子解析器,并且满足注册要求(后面的许多语句与各种错误/健全性检查相关,例如当多个包定义时要做什么运行时实例的相同子命令名称等),注册到该特定运行时实例上的 argparser 实例,并且子解析器被传递到init_argparser封装该子命令的运行时的方法中。例如,子命令子解析器是通过calmjs webpack方法设置的,并且该包将子命令注册在其自己的. (要使用它们,只需安装相关软件包即可)。init_argparserwebpacksetup.pypip

  • 当有人想要添加插件时,此解决方案需要更改 setup.py 中的主要代码,不是吗? (2认同)