argparse - Build back command line

2019-06-18 01:00发布

问题:

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?

回答1:

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.



回答2:

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


回答3:

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.