禁用/删除argparse中的参数

33

是否可以删除或禁用argparse中的参数,使其不在帮助中显示?如何实现?

添加新参数很容易:

parser = argparse.ArgumentParser()
parser.add_argument('--arg1', help='Argument 1')
parser.add_argument('--arg2', help='A second one')

我知道您可以通过指定“resolve”冲突处理程序来覆盖参数的新定义:

#In one script that should stand-alone and include arg1:

parser = argparse.ArgumentParser(conflict_handler='resolve')
parser.add_argument('--arg1', help='Argument 1')
parser.add_argument('--arg2', help='A second one')

#In another script with similar options
parser.add_argument('--arg1', help='New number 1')

但是这仍然在帮助消息和parse_args的结果中包括了 arg1。是否有类似的方法可以

#Wishful thinking
#In another script with similar options, that shouldn't include arg1
parser.remove_argument('--arg1')

还有其他相对容易的方法来实现这个吗?

另外:如果参数是位置参数,这种方法会有所不同吗?

注意:按照这里建议的解析后删除arg1的问题在于该参数仍然显示在帮助中。


为什么不将共享参数分解出来,然后在脚本中实际需要的地方添加每个版本的 arg1 - jonrsharpe
@jonrsharpe 这可能是最好的选择,也是我现在正在做的事情,但有些共同开发者很兴奋地想要在主文件中保留一个单一的命令行解析函数,因此我想寻找其他选择。 - Bryan P
5个回答

15

是否有可能在argparse中删除或禁用参数,以便它不会显示在帮助中?

当您添加参数时,将help设置为argparse.SUPPRESS,如下所示:


parser.add_argument('--arg1', help=argparse.SUPPRESS)

这将防止该参数显示在默认帮助输出中。


3
这仍会解析该参数,并且例如如果将其标记为必需,则需要该参数。 - Lukas Graf
另外,我希望第一个文件能够独立运行,并在输出显示中包含 arg1 的帮助信息。我已经编辑了问题以进行澄清。 - Bryan P
哦,等等,我现在明白了,似乎我可以在第二个文件中使用SUPPRESS。这应该有点用(尽管它仍然将选项视为有效)。但似乎解析位置参数可能会有问题(将此考虑添加到原始问题中)。 - Bryan P

10

尽管hpaulj的回答很好,但在我的情况下,parser._remove_action(action)未从帮助中删除位置参数。它还需要从_action_group中删除:

def remove_argument(parser, arg):
    for action in parser._actions:
        opts = action.option_strings
        if (opts and opts[0] == arg) or action.dest == arg:
            parser._remove_action(action)
            break

    for action in parser._action_groups:
        for group_action in action._group_actions:
            opts = group_action.option_strings
            if (opts and opts[0] == arg) or group_action.dest == arg:
                action._group_actions.remove(group_action)
                return

要移除 --bar,请调用:

remove_argument(parser, "bar")

2
代码没有使用示例。您必须传递一个没有前导破折号的参数才能使其工作,例如要删除 --bar,请调用 remove_option(parser, "bar") - stason
我喜欢你。这个工作得非常好。谢谢@Lorenzo。 - Eric Hansen
这个解决方案对我很有效,但我不明白为什么他们没有将其作为ArgsParser类本身的一部分。 - a_girl

8

删除argparse选项的函数

def remove_options(parser, options):
    for option in options:
        for action in parser._actions:
            if vars(action)['option_strings'][0] == option:
                parser._handle_conflict_resolve(None,[(option,action)])
                break

3
有趣。这样做会将其从帮助中移除并防止选项被解析吗?你可以加一些解释和示例吗? - Bryan P

4

尽管我下面提到了bug问题,但你使用的resolve方法是一个可能的方法。这不适合新手或需要遵循公共API的人。

parser有一个Action(参数)对象列表(由add_argument创建)。

使用您的第二个解析器定义,它的_actions列表如下:

In [22]: parser._actions
Out[22]: 
[_HelpAction(option_strings=['-h', '--help'], dest='help'...),
 _StoreAction(option_strings=['--arg2'], dest='arg2', nargs=None,
      const=None, default=None, type=None, choices=None, 
      help='A second one', metavar=None),
 _StoreAction(option_strings=['--arg1'], dest='arg1', nargs=None,
      const=None, default=None, type=None, choices=None, 
      help='New number 1', metavar=None)]

当您使用 resolve 添加一个与现有操作冲突的操作时,它会删除冲突的现有操作。详见 _handle_conflict_resolve 方法。但是我可以通过欺骗来删除一个动作而不添加新动作。
In [23]: parser._handle_conflict_resolve(None, [('--arg1',parser._actions[2])])

查看 _actions 并确认 --arg1 已删除。

In [24]: parser._actions
Out[24]: 
[_HelpAction(option_strings=['-h', '--help'], dest='help',....),
 _StoreAction(option_strings=['--arg2'], dest='arg2', nargs=None,...)]

In [25]: parser.print_help()
usage: ipython3 [-h] [--arg2 ARG2]

optional arguments:
  -h, --help   show this help message and exit
  --arg2 ARG2  A second one

resolve 只处理 optionals,这些选项字符串可能会产生冲突。如果存在冲突的标志,则首先删除它们,只有在没有标志剩余时才删除冲突的操作。因此,在同时使用短选项和长选项时要特别小心。

这并不涉及位置参数的情况。它们没有标志,并且它们可以共享 dest 参数(除非它们是附加操作,否则只会出现一个参数结果)。

In [27]: foo1 = parser.add_argument('foo',help='foo 1 positional')
In [28]: foo2 = parser.add_argument('foo',help='foo 2 positional')
In [29]: parser.print_help()
usage: ipython3 [-h] [--arg2 ARG2] foo foo
positional arguments:
  foo          foo 1 positional
  foo          foo 2 positional
  ...

进一步尝试后,我发现可以移除其中一个新位置参数:

In [33]: parser._actions[-1]
Out[33]: _StoreAction(option_strings=[], dest='foo',... help='foo 2 positional', metavar=None)
In [35]: foo2=parser._actions[-1]
In [36]: foo2.container._remove_action(foo2)
In [39]: parser.print_help()
usage: ipython3 [-h] [--arg2 ARG2] foo    
positional arguments:
  foo          foo 1 positional
 ....

如果我选择_actions[-2],我将删除第一个foo。如果我将add_argument返回的值赋给一个变量,例如foo1,我可以使用它来替代在parser._actions列表中查找该值。运行一个示例解析器(我使用IPython)并查看这些对象可能会有所帮助。
同样,这似乎适用于简单的示例,但如果与更复杂的内容(或生产环境)一起使用,则需要仔细测试。
该主题在几年前提到了Python错误/问题:http://bugs.python.org/issue19462 Add remove_argument() method to argparse.ArgumentParser 我讨论了完全删除的困难,并提出了一些替代方案。可以使用argparse.SUPPRESS来隐藏帮助信息。如果不需要,可以忽略optionals。然而,positionals则比较棘手,尽管我建议调整它们的属性(nargsdefault)。但是已经过了一段时间,所以我需要重新审查那些帖子。
=============================
我对@2rs2ts的问题感到好奇(请参见评论)。
我创建了一个解析器,然后将其用作另一个解析器的父级(不需要使用子解析器机制)。然后我从一个解析器中删除了一个参数,并查看了另一个解析器中的更改。
创建一个具有一个参数的父解析器:
In [59]: p=argparse.ArgumentParser()
In [60]: p.add_argument('--foo')
Out[60]: _StoreAction(option_strings=['--foo'], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)

使用parents创建另一个:
In [61]: p1=argparse.ArgumentParser(parents=[p],add_help=False)
In [62]: p1._actions
Out[62]: 
[_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help='show this help message and exit', metavar=None),
 _StoreAction(option_strings=['--foo'], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)]

请注意,两个解析器的第二个操作是相同的(相同的 id)。parents 只是复制了对原始 --foo 操作的引用,而没有进行复制。
In [63]: id(p._actions[1])
Out[63]: 3000108652
In [64]: id(p1._actions[1])
Out[64]: 3000108652

现在,使用我之前想出的技巧,从一个解析器中删除“--foo”:

In [65]: p1._handle_conflict_resolve(None,[('--foo',p1._actions[1])])
In [66]: p1._actions
Out[66]: [_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help='show this help message and exit', metavar=None)]

'--foo' 已经从 p1 列表中删除,但仍然在 p 列表中存在。但是 option_strings 现在为空。

In [67]: p._actions
Out[67]: 
[_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, help='show this help message and exit', metavar=None),
 _StoreAction(option_strings=[], dest='foo', nargs=None, const=None, default=None, type=None, choices=None, help=None, metavar=None)]
resolve代码删除了--foo操作中的冲突的option_strings,然后将其从p1._actions列表中删除。但是更改p1引用的option_strings也会同时更改p引用。 argparse使用了几种区分positionalsoptionals的方法,但在解析时最常用的方法是查看option_strings属性是否为空。通过清空此属性,resolve实际上将一个optional变成了一个positional
糟糕,我的记忆不如应该的好。:)一年前我回答了一个涉及parentsresolve的类似问题。 https://dev59.com/woPba4cB1Zd3GeqPrFoD#25821043argparse conflict resolver for options in subcommands turns keyword argument into positional argument

很好的发现。似乎 argparse 的开发人员可以使用这个机制来制作官方支持的实现。 - Bryan P
2
这会对子解析器造成严重影响。我有一个基础子解析器和一堆其他的子解析器,它们将该基础解析器标记为它们的“parent”。如果我从一个子解析器中删除一个可选参数,那么所有其他子解析器仍然具有该参数...但它变成了位置参数(但在它们的帮助消息中仍然显示为可选!)Burhan的方法也是如此,所以这可能是库的限制。 - 2rs2ts
我添加了一个解释和一个链接到之前的SO,这个问题几乎相同。 - hpaulj

0
这对我有效:
def disable_argument(parser: argparse.ArgumentParser, arg: str) -> None:
    """Disable an argument from a parser.

    Args:
        parser (argparse.ArgumentParser): Parser.
        arg (str): Argument to be removed.
    """
    def raise_disabled_error(action):
        """Raise an argument error."""
        def raise_disabled_error_wrapper(*args) -> str:
            """Raise an exception."""
            raise argparse.ArgumentError(action, f'Has been disabled!')
        return raise_disabled_error_wrapper

    for action in parser._actions:
        opts = action.option_strings
        if (opts and opts[0] == arg) or action.dest == arg:
            action.type = raise_disabled_error(action)
            action.help = argparse.SUPPRESS
            break

如果使用了disabled参数并将其从帮助中删除,这将引发错误。


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