Disable/Remove argument in argparse
Asked Answered
L

5

33

Is it possible to remove or disable an argument in argparse, such that it does not show in the help? How?

It is easy to add new arguments:

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

And I know you can override arguments with a new definition by specifying the "resolve" conflict handler:

#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')

But this still includes arg1 in the help message and results of parse_args Is there anything like

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

Or another reasonably easy way to achieve this?

Also: Would the approach be different if the argument was a positional argument?

Note: the problem with removing arg1 after parsing as suggested here is that the argument still shows in the help

Lacedaemon answered 27/9, 2015 at 11:14 Comment(2)
Why not factor out the shared parameters, then add each version of arg1 in the script where it's actually needed?Bairn
@Bairn That may be the best option and is what I am doing now, but some co-developers are excited to keep a single command-line parsing function in the main, first, file so I wanted to look for alternatives.Lacedaemon
C
4

Despite the bug issue I mention below, your use of resolve suggests a possible method. This is not for the novice or someone who needs to stick with the public API.

The parser has a list of the Action (argument) objects (as created by add_argument).

Using your 2nd parser definition, its _actions list is:

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)]

When you add a conflicting one with resolve, it removes the existing Action that conflicts. Look at the _handle_conflict_resolve method for details. But I can fool it into removing an action without adding a new one.

In [23]: parser._handle_conflict_resolve(None, [('--arg1',parser._actions[2])])

Look at _actions and help to verify that --arg1 is gone.

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 just handles optionals, ones where the flag strings might conflict. And it removes conflicting flags first, removing the conflicting action only if no flags remain. So be extra careful when you have both short and long options.

And this does not address the case of positionals. They don't have flags, and they may share dest parameters. (though only one will appear in the result, unless they are appending actions).

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
  ...

Playing around a bit more, it looks like I can remove one of these new positionals:

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
 ....

If I'd choosen _actions[-2] I would have removed the first foo. If I assign the value that add_argument returns to a variable, e.g. foo1, I can use that instead of looking up the value in the parser._actions list. It may be helpful to run a sample parser in an interative shell (I use IPython) and look at these objects.

Again, this seems to work on a simple example, but it needs careful testing if used with something more complex (or for production).


The topic was raised on the Python bugs/issues a couple of years ago:

http://bugs.python.org/issue19462 Add remove_argument() method to argparse.ArgumentParser

I discussed the difficulties in complete removal, and suggested some alternatives. argparse.SUPPRESS can be used to hide helps. optionals can be ignored if they aren't required. positionals are trickier, though I suggested tweaking their attributes (nargs and default). But it's been a while, so I need to review those posts.

=============================

I was curious about @2rs2ts problem (see comment).

I made a parser, and then used it as parent to another parser (no need to use the subparser mechanism). Then I removed an argument from one parser, and looked at changes in the other parser.

Make a parent parser with one argument:

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)

Make another with 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)]

Note that the 2nd Action is the same for both parsers (same id). parents just copied a reference to the original --foo Action, it did not make a copy.

In [63]: id(p._actions[1])
Out[63]: 3000108652
In [64]: id(p1._actions[1])
Out[64]: 3000108652

Now remove '--foo' from one parser, using the trick I worked out before:

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' is gone from p1 list, but still present in the p list. But option_strings is now empty.

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)]

The resolve code removed conflicting option_strings from the --foo action, and then removed it from the p1._actions list. But changing option_strings for the p1 reference changed the p reference as well.

argparse uses a couple of ways to distinguish positionals from optionals, but the one used most often in parsing is to look at whether the option_strings attribute is empty or not. By emptying this attribute, resolve has effectively turned an optional into a positional.

Oops, my memory isn't what it should be.:) A year ago I answered a similar question involving parents and resolve

https://mcmap.net/q/453414/-argparse-conflict-resolver-for-options-in-subcommands-turns-keyword-argument-into-positional-argument argparse conflict resolver for options in subcommands turns keyword argument into positional argument

Chantay answered 27/9, 2015 at 15:34 Comment(3)
Great find. Seems argparse developers could use this machinery to make an official, supported implementation.Lacedaemon
This wreaks havoc on subparsers. I have a base subparser and a bunch of other ones that mark that base as their parent. If I remove an optional argument from one subparser, then all the other subparsers still have the argument... but it becomes positional (but shows as optional in their help messages, still!) The same thing happens for Burhan's method too, so it's probably a limitation of the library.Usurp
I added an explanation and a link to an earlier SO virtually the same problem.Chantay
L
17

Is it possible to remove or disable an argument in argparse, such that it does not show in the help?

Set help to argparse.SUPPRESS when you add the argument, like this:

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

This will prevent the argument from showing up in the default help output.

Liar answered 27/9, 2015 at 11:26 Comment(3)
This will still parse the argument, and for example have it be required if it's flagged as such.Streetlight
Also, I would like the first file to run stand-alone and include the arg1 help in the output display. I've edited the question to help clarify.Lacedaemon
Oh, wait, I see now it seems I could use SUPPRESS in the second file. That should sort of work (although it does still treat the option as valid). But seems that parsing a positional argument might be problematic (added this consideration to original question.Lacedaemon
B
10

Although hpaulj answer is great, in my case parser._remove_action(action) was not removing positional arguments from the help. It also needed to be removed from the _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

To remove --bar, call:

remove_argument(parser, "bar")
Blasting answered 10/4, 2018 at 12:26 Comment(3)
The code doesn't have a usage example. You have to pass an argument without leading dashes for it to work, e.g. to remove --bar, call remove_option(parser, "bar")Indeterminism
I love you. This works so well. Thank you @Lorenzo.Misbehave
this solution worked for me, but I don't understand why haven't they made it a part of ArgsParser class itselfFallen
T
8

Function to remove argparse options:

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
Tiberius answered 26/4, 2016 at 11:29 Comment(1)
Interesting. Will this both remove it from help and prevent the option from being parsed? Might you add a bit more explanation and an example?Lacedaemon
C
4

Despite the bug issue I mention below, your use of resolve suggests a possible method. This is not for the novice or someone who needs to stick with the public API.

The parser has a list of the Action (argument) objects (as created by add_argument).

Using your 2nd parser definition, its _actions list is:

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)]

When you add a conflicting one with resolve, it removes the existing Action that conflicts. Look at the _handle_conflict_resolve method for details. But I can fool it into removing an action without adding a new one.

In [23]: parser._handle_conflict_resolve(None, [('--arg1',parser._actions[2])])

Look at _actions and help to verify that --arg1 is gone.

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 just handles optionals, ones where the flag strings might conflict. And it removes conflicting flags first, removing the conflicting action only if no flags remain. So be extra careful when you have both short and long options.

And this does not address the case of positionals. They don't have flags, and they may share dest parameters. (though only one will appear in the result, unless they are appending actions).

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
  ...

Playing around a bit more, it looks like I can remove one of these new positionals:

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
 ....

If I'd choosen _actions[-2] I would have removed the first foo. If I assign the value that add_argument returns to a variable, e.g. foo1, I can use that instead of looking up the value in the parser._actions list. It may be helpful to run a sample parser in an interative shell (I use IPython) and look at these objects.

Again, this seems to work on a simple example, but it needs careful testing if used with something more complex (or for production).


The topic was raised on the Python bugs/issues a couple of years ago:

http://bugs.python.org/issue19462 Add remove_argument() method to argparse.ArgumentParser

I discussed the difficulties in complete removal, and suggested some alternatives. argparse.SUPPRESS can be used to hide helps. optionals can be ignored if they aren't required. positionals are trickier, though I suggested tweaking their attributes (nargs and default). But it's been a while, so I need to review those posts.

=============================

I was curious about @2rs2ts problem (see comment).

I made a parser, and then used it as parent to another parser (no need to use the subparser mechanism). Then I removed an argument from one parser, and looked at changes in the other parser.

Make a parent parser with one argument:

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)

Make another with 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)]

Note that the 2nd Action is the same for both parsers (same id). parents just copied a reference to the original --foo Action, it did not make a copy.

In [63]: id(p._actions[1])
Out[63]: 3000108652
In [64]: id(p1._actions[1])
Out[64]: 3000108652

Now remove '--foo' from one parser, using the trick I worked out before:

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' is gone from p1 list, but still present in the p list. But option_strings is now empty.

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)]

The resolve code removed conflicting option_strings from the --foo action, and then removed it from the p1._actions list. But changing option_strings for the p1 reference changed the p reference as well.

argparse uses a couple of ways to distinguish positionals from optionals, but the one used most often in parsing is to look at whether the option_strings attribute is empty or not. By emptying this attribute, resolve has effectively turned an optional into a positional.

Oops, my memory isn't what it should be.:) A year ago I answered a similar question involving parents and resolve

https://mcmap.net/q/453414/-argparse-conflict-resolver-for-options-in-subcommands-turns-keyword-argument-into-positional-argument argparse conflict resolver for options in subcommands turns keyword argument into positional argument

Chantay answered 27/9, 2015 at 15:34 Comment(3)
Great find. Seems argparse developers could use this machinery to make an official, supported implementation.Lacedaemon
This wreaks havoc on subparsers. I have a base subparser and a bunch of other ones that mark that base as their parent. If I remove an optional argument from one subparser, then all the other subparsers still have the argument... but it becomes positional (but shows as optional in their help messages, still!) The same thing happens for Burhan's method too, so it's probably a limitation of the library.Usurp
I added an explanation and a link to an earlier SO virtually the same problem.Chantay
V
0

This works for me:

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

This will raise an error if the disabled argument is used and it removes the argument from the help.

Virge answered 21/3, 2023 at 18:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.