Bryan P Bryan P - 7 months ago 10
Python Question

Disable/Remove argument in argparse

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

Answer

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

http://stackoverflow.com/a/25821043/901925 argparse conflict resolver for options in subcommands turns keyword argument into positional argument

Comments