如何为argparse子命令添加常用参数?

15

在使用argparse时,一些子命令需要相同的选项,我使用parents来避免在每个子命令中重复定义它们。

脚本文件名: testarg.py

import argparse                                                                  

parser = argparse.ArgumentParser(add_help=False)                                 
parser.add_argument('-H', '--host', default='192.168.122.1')                     
parser.add_argument('-P', '--port', default='12345')                             
subparsers = parser.add_subparsers()                                             

# subcommand a                                                                   
parser_a = subparsers.add_parser('a', parents=[parser])                          
parser_a.add_argument('-D', '--daemon', action='store_true')                     

parser_a.add_argument('-L', '--log', default='/tmp/test.log')                    

# subcommand b                                                                   
parser_b = subparsers.add_parser('b', parents=[parser])                          
parser_b.add_argument('-D', '--daemon', action='store_true')                     

# subcommand c                                                                   
parser_c = subparsers.add_parser('c', parents=[parser])                          
args = parser.parse_args()                                                       

print args   

但是当我运行命令时:

>>>./testarg.py a
usage: testarg.py a [-h] [-H HOST] [-P PORT] [-D] [-L LOG] {a,b,c} ...
testarg.py a: error: too few arguments

期望输出:

>>>./testarg.py a
Namespace(daemon=False, host='192.168.122.1', log='/tmp/test.log', port='12345')

>>>./testarg.py b -H 127.0.0.1 -P 11111
Namespace(daemon=False, host='127.0.0.1', port='11111')

>>>./testarg.py c
Namespace(host='192.168.122.1', port='12345')

also, 

>>>./testarg.py c -H 127.0.0.1 -P 12222
Namespace(host='127.0.0.1', port='12222')

我错过了什么?


请问您能否解释一下您想要达成什么目的? - Konstantin
@Alik,回答已更新。我想使用相同的选项(-H-P)运行一些子命令,但我不想在每个子命令中都添加它们。 - Fujiao Liu
2个回答

41

创建一个独立的父解析器并将其传递给子解析器

import argparse                                                                  

parent_parser = argparse.ArgumentParser(add_help=False)                                 
parent_parser.add_argument('-H', '--host', default='192.168.122.1')                     
parent_parser.add_argument('-P', '--port', default='12345')                             

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

# subcommand a                                                                   
parser_a = subparsers.add_parser('a', parents = [parent_parser])                          
parser_a.add_argument('-D', '--daemon', action='store_true')                     

parser_a.add_argument('-L', '--log', default='/tmp/test.log')                    

# subcommand b                                                                   
parser_b = subparsers.add_parser('b', parents = [parent_parser])                          
parser_b.add_argument('-D', '--daemon', action='store_true')                     

# subcommand c                                                                   
parser_c = subparsers.add_parser('c', parents = [parent_parser])                          
args = parser.parse_args()                                                       

print args   

这将产生期望的结果

$ python arg.py a
Namespace(daemon=False, host='192.168.122.1', log='/tmp/test.log', port='12345')
$ python arg.py b -H 127.0.0.1 -P 11111
Namespace(daemon=False, host='127.0.0.1', port='11111')
$ python arg.py c
Namespace(host='192.168.122.1', port='12345')

谢谢,它有效。我读了官方的 argparse 文档,但没有找到这个方法,你是怎么知道的?有什么文档吗? - Fujiao Liu
文档中有一个“parents”部分和一个“subparsers”部分,你已经阅读过了。你正在以一种意想不到的方式将它们结合在一起。很难预测所有的应用场景。 - hpaulj
1
这里的 add_help,即 parser = argparse.ArgumentParser(add_help=False) 是不必要的。 - hyankov
2
我需要在parent_parser中使用add_help=False,但在parser中不需要。如果在父级解析器上没有这个选项,它会抛出一个错误。在parent_parser上设置add_help=False并不能阻止父级解析器选项在parser的--help中被列出。 - Alcamtar
这是一个不错的解决方案!但要注意:目前在CPython中存在一个错误,可能会在子命令之前提供的选项被静默跳过:https://github.com/python/cpython/pull/30146 - Lucas Cimon

9
当您将parser本身用作子解析器的parents时,您会递归地向每个子解析器添加subparsers。实际上,add_subparsers命令定义了一个位置参数,该参数获取选择项{'a','b','c'}。它最终期望prog.py a a a ...,每个子解析器都期望另一个子解析器命令等等。
我从未见过有人尝试这种定义方式,需要一些思考才能意识到发生了什么。 @Alik's的方法是正确的。单独定义父解析器,并且不直接使用它。它只是那些您想要添加到每个子解析器中的-H-P操作的源。这就是您想要添加到子解析器中的全部内容。
另一种方法是在主解析器中简单地定义-H-P
parser = argparse.ArgumentParser()
parser.add_argument('-H', '--host', default='192.168.122.1')
parser.add_argument('-P', '--port', default='12345')
subparsers = parser.add_subparsers()

# subcommand a
parser_a = subparsers.add_parser('a')
parser_a.add_argument('-D', '--daemon', action='store_true')
....

它的功能与以前一样,只是在使用子命令之前必须指定-H-P
0015:~/mypy$ python stack33645859.py -H 127.0.0.1 -P 1111 b
Namespace(daemon=False, host='127.0.0.1', port='1111')

它们仍然以相同的方式出现在命名空间中,只是在命令行中的顺序不同。但是help也会有所不同。

第三个选项是通过循环或函数以编程方式添加常见参数。一个粗略的例子如下:

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
splist = []
for cmd in ['a','b','c']:
    p = subparsers.add_parser(cmd)
    p.add_argument('-H', '--host', default='192.168.122.1')
    p.add_argument('-P', '--port', default='12345')
    splist.append(p)
splist[0].add_argument('-D', '--daemon', action='store_true')

功能上,它与@Alik的方法类似,但有微小的区别。使用parent时,只创建了一对H和P动作对象,并向每个子解析器添加引用。
在我的方法中,每个子解析器都有自己的H和P动作对象。每个子解析器可以为这些参数定义不同的默认值。我记得在其他一个SO问题中,这是一个问题。
编码工作在所有情况下都是相似的。

感谢您的详细解释,非常感谢。实际上,我尝试了您建议的两种方法。我之所以提出这些问题,是因为我认为“-P”和“-H”是每个子命令的一部分,而不是根解析器的一部分,“-H”和“-P”必须在指定子命令之前指定,这不是我想要的。您有没有关于如何使用Pythonic公共选项进行子命令的建议? - Fujiao Liu
1
我认为我的最后一个建议看起来非常Pythonic。 - hpaulj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接