1

Can I extract only the parsed arguments from the command line, ignoring all default arguments? If the user passed the default value for an argument, I would like it to appear in the subset as well:

parser = argparse.ArgumentParser()
parser.add_argument("--a",type=str,default='a')
parser.add_argument("--b",type=str,default='b')
parser.add_argument("--c",type=str,default='c')
parser.add_argument("--d",type=str,default='d')
python run.py --a "e" --b "b"

I would like to have

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--a",type=str,default='a')
    parser.add_argument("--b",type=str,default='b')
    parser.add_argument("--c",type=str,default='c')
    parser.add_argument("--d",type=str,default='d')

    from_cli = parse_from_cli(parser) # from_cli = {'a':'e','b':'b'}
DsCpp
  • 2,259
  • 3
  • 18
  • 46
  • 3
    Why do you have defaults if you don't want them to be used when the user omits the option? – Barmar Jun 13 '21 at 05:24
  • use-case has been added. – DsCpp Jun 13 '21 at 12:10
  • Read the old dictionary in first, and use the values there as the defaults in argparse. – Barmar Jun 13 '21 at 16:58
  • If `default=argparse.SUPPRESS`, the `args` will only contain attributes set by the user. Include a `print(args)` while debugging to see clearly what the parser has produced. – hpaulj Jun 13 '21 at 17:42
  • But that way I won't be able to set default parameters. These are two orthogonal things. Parse the arguments, and understand which flags were passed by the user. I would like the latter. – DsCpp Jul 14 '21 at 13:13
  • I deleted the Use case section as it seems to mislead answers, sorry! I simply want to parse only the arguments provided from the command line – DsCpp Jul 16 '21 at 20:04

3 Answers3

3

You can provide parse_args with a namespace argument. In this case, the values in the provided namespace will take precedence over the defaults set in the parser. We just need to create a "mask" namespace and a sentinel object to cover the defaults. It does not appear there is a public method for extracting the args from a parser, but we can call the parser twice and use the first call to create the mask.

import argparse

NOTHING = object()

parser = argparse.ArgumentParser()
parser.add_argument('--foo', default=1, type=int)
parser.add_argument('--bar', default=2, type=int)

args = vars(parser.parse_args())
mask = argparse.Namespace(**{arg: NOTHING for arg in args})

masked_namespace = parser.parse_args(namespace=mask)
masked_args = {
    arg: value
    for arg, value in vars(masked_namespace).items()
    if value is not NOTHING
}

print(masked_args)

In use you'll get:

$ python foo.py
{}
$ python foo.py --bar 11
{'bar': 11}
Kyle Parsons
  • 1,475
  • 6
  • 14
  • This is a great answer! I wasn't familiar with that and this indeed helps. Unfortunately, this was not the question (I deleted the use case section as it was probably a bit misleading). I am trying to extract a namespace/dict containing only the flags that came from the cli. Isn't it possible? – DsCpp Jul 16 '21 at 20:09
  • I've updated the answer to be independent of the removed "use case" section. – Kyle Parsons Jul 16 '21 at 21:03
  • A-m-a-z-i-n-g. Thank you – DsCpp Jul 16 '21 at 21:15
  • Wow! This is so useful for my programs when I want to resume from a breakpoint but with different properties than originally specified. Thanks! – Hang Chen Sep 01 '23 at 20:12
0

You can omit the default= argument to the add_argument function, then arguments that the user doesn't specify explicitly will resolve to None.

j3st
  • 345
  • 1
  • 8
0

You can keep the old arguments in a separate config file, then read and write back changed arguments.

This should look something like this:

import argparse

args = {'a':None,'b':None}

with open("config","r") as config:

    parser = argparse.ArgumentParser()
    parser.add_argument("--a",type=str)
    parser.add_argument("--b",type=str)

    newargs = parser.parse_args()


    # set new arguments
    args['a'] = newargs.a
    args['b'] = newargs.b

    # read previous arguments
    for line in config.readlines():
        if ":" in line : 
            temp = line.strip().split(":")
            if not args[temp[0]] :
                args[temp[0]] = temp[1]

with open("config","w") as config:

    # write back new arguments with the newargs
    lines = []
    for key in args.keys() :
        lines.append("{}:{}\n".format(key,args[key]))

    config.writelines(lines)

print(args)

The encoding in the config file could of course be different, I choose this one for debugging purpose.

Torche
  • 21
  • 5
  • I deleted the Use case section as it seems to mislead answers, sorry! I simply want to parse only the arguments provided from the command line – DsCpp Jul 16 '21 at 20:04