使用 argparse 输出调用函数

Posted

技术标签:

【中文标题】使用 argparse 输出调用函数【英文标题】:Using the argparse output to call functions 【发布时间】:2011-03-28 03:56:11 【问题描述】:

目前我的代码如下所示。它允许我解析我的程序脚本获取的多个参数。是否有更接近“最佳实践”的不同方式?我还没有看到实际使用argparse 输出的代码,只知道如何设置它。

def useArguments():
    x = 0
    while x <= 5:
        if x == 0:                      
            if args.getweather != None:
                getWeather(args.getweather)
        if x == 1:
            if args.post != None:
                post(args.post)
        if x == 2:
            if args.custompost != None:
                custompost(args.custompost)
        if x == 3:
            if args.list != None:
                listAccounts(args.list)
        if x == 4:
            if args.add != None:
                addAccount(args.add[0])
        if x == 5:
            if args.edit != None:
                editAccount(args.edit[0])
        x = x + 1    


if __name__ == '__main__':

    updateConfig()

    parser = argparse.ArgumentParser(description='Post Yahoo weather to Twitter.', epilog="Report any bugs to example@email.com", prog='Program')

    parser.add_argument('-a', '--add', nargs=1, help='Add a new account. Use the desired account name as an argument.')
    parser.add_argument('-e', '--edit', nargs=1, choices=accountListSTR[:-1], help='Edit an account. Use the desired account name as an argument.')
    parser.add_argument('-g', '--getweather', nargs='*', choices=accountListSTR, help='Get weather and post here. Specify account(s) as argument. Use "all" for all accounts. If you specify multiple accounts, separate by a space NOT a comma.')
    parser.add_argument('-p', '--post', nargs='*', choices=accountListSTR, help='Post weather to Twitter. Specify account(s) as argument. Use "all" for all accounts. If you specify multiple accounts, separate by a space NOT a comma.')
    parser.add_argument('-c', '--custompost', nargs=2, help='Post a custom message. Specify an account then type the message. Make sure you use "" around the message. Use "all" for all accounts.')
    parser.add_argument('-l', '--list', action='store_const', const='all', help='List all accounts.')
    parser.add_argument('--version', action='version', version='%(prog)s 0.3.3')

    args = parser.parse_args()

    useArguments()

【问题讨论】:

【参考方案1】:

您可以为参数 by 提供自定义 action,我引用:

传递一个实现 动作 API。最简单的方法 是扩展argparse.Action, 提供适当的__call__ 方法。 __call__ 方法应该 接受四个参数:

    parser:包含此操作的 ArgumentParser 对象。 命名空间:将由parse_args() 返回的命名空间对象。大多数操作都会向该对象添加一个属性。 values:关联的命令行参数,应用了任何类型转换。(类型转换使用add_argument() 的 type 关键字参数指定。 option_string:用于调用此操作的选项字符串。 option_string 参数是可选的,如果操作与位置参数相关联,则该参数将不存在。

【讨论】:

在什么情况下这是最好的方法?我看不到所有这些额外代码的用途。但是话又说回来,我几乎没有使用类,所以我可能遗漏了一些东西。 @vlad,它可能用于在提供参数时自动调用函数,这就是你正在做的所有样板文件 - 你只需要将函数设为__call__ 的适当子类的 argparse.Action 方法。但是,如果您没有“获得”面向对象的编程,那没关系,您可以按照自己的方式进行(尽管该循环和 if x == 检查在任何情况下都是多余的 - 只需依次检查哪些参数可能是适当调用的跟随者,在您使用的其他样板中没有附加值)。 接受了这个答案,因为它回答了我的问题。我可能最终会尝试这个来了解它是如何工作的;但这需要对我的代码当前的工作方式进行许多更改(尤其是那里列出的功能)。谢谢! 我喜欢这种方法的简洁性,但限制是自定义操作在解析剩余参数之前开始(即,它们不在 Namespace() 中),所以不清楚可选标志可能会影响自定义操作的行为。【参考方案2】:

见http://docs.python.org/library/argparse.html#sub-commands:

处理子命令的一种特别有效的方法是将add_subparsers() 方法的使用与对set_defaults() 的调用结合起来,这样每个子解析器都知道它应该执行哪个Python 函数。

简而言之:

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

weather_parser = subparsers.add_parser('get-weather')
weather_parser.add_argument('--bar')
weather_parser.set_defaults(function=get_weather)  # !

args = parser.parse_args(['get-weather', '--bar', 'quux'])
print args.function(args)

在这里,我们为命令get-weather 创建一个子解析器,并将函数get_weather 分配给它。

请注意,文档说关键字/属性被命名为 func,但在 argparse 1.1 中它绝对是 function

生成的代码有点冗长,所以我发布了一个小包"argh",它使事情变得更简单,例如:

parser = argparse.ArgumentParser()
add_commands(parser, [get_weather])
print dispatch(parser, ['get-weather', '--bar', 'quux'])

“Argh”可以做更多,但我会让堆栈溢出来回答这个问题。 :-)

【讨论】:

关于 caffinatedmonkey 最近的编辑(“我不会让这个答案堆栈溢出”→“我会让堆栈溢出回答那个”)。新版本听起来不错,但实际上我的意思是,如果我在 textarea 中塞入太多信息,答案会变得臃肿。 =) 您确定func/function 部分仍然正确吗? func 对我来说很好...... 子解析方法不仅丑陋,而且似乎只是将参数解析的任务传递给了较低的级别——你要么必须有单独的函数来传递给每个 func 然后调用您的“真实”功能,或者您的真实功能必须采用通用 args 字典。我想前者对于大型程序来说很好,但是在一个小程序中引入这么多额外的层似乎很荒谬。尽管如此,子解析器似乎是迄今为止最好的选择。 @RyneEverett 实际上,argh 库通过自省签名支持将普通 Python 函数作为命令,因此不需要愚蠢的包装函数。 我的批评没有针对 argh,但我肯定会检查一下。 Argh 似乎本质上是 argparse 子解析器的包装器,所以我对 argparse 的样板要求的烦恼仍然存在。【参考方案3】:

除了--version,这是一个非常常见的选项,您提供的操作最好被视为“子命令”。

我不知道 argparse 的细节,因为我还没有尝试过 Python 2.7,但您可以看看 svn 命令作为示例,这里是命令行的一些伪代码:

myprog [--version] <command> [<command opts>...]

&lt;command&gt; 在哪里:

add|edit|getweather|post|custompost|list

&lt;command opts&gt; 是特定于该命令的选项。使用 optparse(类似),这意味着您的命令将在 args 中返回,当调用 parse_args 时,您可以执行以下操作:

opts, args = parser.parse_args()
if opts.version:
    ...
else:
    getattr("do_" + args[0])(*args[1:])

我发现这种模式对调试特别有用,我可以从命令行提供对内部函数的访问,并传递各种参数进行测试。根据您自己的项目调整命令处理程序的选择。

【讨论】:

argparse 实际上正是为这种模式提供了支持。 (optparse 没有。)我自己仍然坚持使用 Python 2.6,所以我也不知道具体细节,但在 documentation 中有解释。 这样做有什么好处,而不是仅仅从我的论点中删除--。我猜对最终用户来说它最终看起来是一样的,对吧?我想这样对用户也更有意义,因为他们并没有真正使用自定义参数运行程序,而是告诉它做某事。 @vlad003:没错。程序的用户必须选择其中一个且只有一个“选项”,因此它们实际上是对您的程序的命令。然而,这些命令确实可能需要参数。另一种方法是为每个命令编写单独的可执行脚本,在每个命令中分别执行 arg 解析,并调用一个公共代码库来实现。例如,您的脚本可能被称为:myprog-list、myprog-add 等。但是我可能会添加,因为您使用的是 Python-2.7 中的 argparse,可能已经包含了非常精美的参数处理。

以上是关于使用 argparse 输出调用函数的主要内容,如果未能解决你的问题,请参考以下文章

如何在单元测试中使用 argparse 参数调用函数?

python 3 argparse调用一个函数

python调用使用argparser的模块

如何在 sys.argv 存在的情况下使用 argparse

使用 optparse 调用函数

使用 argparse 将参数发送到 Python 脚本中的函数