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
arg1
in the script where it's actually needed? – Bairn