In Python, how can I parse the command line, edit the resulting parsed arguments object and generate a valid command line back with the updated values?
For instance, I would like python cmd.py --foo=bar --step=0
call python cmd.py --foo=bar --step=1
with all the original --foo=bar
arguments, potentially without extra arguments added when default value is used.
Is it possible with argparse
?
argparse
is clearly designed to go one way, from sys.argv
to the args
namespace. No thought has been given to preserving information that would let you map things back the other way, much less do the mapping itself.
In general, multiple sys.argv
could produce the same args
. You could, for example, have several arguments that have the same dest
. Or you can repeat 'optionals'. But for a restricted 'parser' setup there may be enough information to recreate a usable argv
.
Try something like:
parser = argparser.ArgumentParser()
arg1 = parser.add_argument('--foo', default='default')
arg2 = parser.add_argument('bar', nargs=2)
and then examine the arg1
and arg2
objects. They contain all the information that you supplied to the add_argument
method. Of course you could have defined those values in your own data structures before hand, e.g.
{'option_string':'--foo', 'default':'default'}
{'dest':'bar', 'nargs':2}
and used those as input to add_argument
.
While the parser may have enough information to recreate a useable sys.argv
, you have to figure out how to do that yourself.
default=argparse.SUPPRESS
may be handy. It keeps the parser from adding a default entry to the namespace. So if the option isn't used, it won't appear in the namespace.
You can use argparse
to parse the command-line arguments, and then modify those as desired. At the moment however, argparse
lacks the functionality to work in reverse and convert those values back into a command-line string. There is however a package for doing precisely that, called argunparse
. For example, the following code in cmd.py
import sys
import argparse
import argunparse
parser = argparse.ArgumentParser()
unparser = argunparse.ArgumentUnparser()
parser.add_argument('--foo')
parser.add_argument('--step', type=int)
kwargs = vars(parser.parse_args())
kwargs['step'] += 1
prefix = f'python {sys.argv[0]} '
arg_string = unparser.unparse(**kwargs)
print(prefix + arg_string)
will print the desired command line:
python cmd.py --foo=bar --step=1
This isn't possible in any easy way that I know of, then again I've never needed to do this.
But with the lack of information in the question in regards to how you call your script, I'll assume the following:
python test.py cmd --foo=bar --step=0
And what you could do is do:
from sys import argv
for index in range(1, len(argv)): # the first object is the script itself
if '=' in argv[index]:
param, value = argv[index].split('=', 1)
if param == '--step':
value = '1'
argv[index] = param + '=' + value
print(argv)
Note that this is very specific to --step
and may be what you've already thought of and just wanted a "better way", but again, I don't think there is.