7

Is there a way, when using argparse, to tell whether a field has a value because the user specified it or because it was not specified and got the default value? Note: I would also like to consider the case where the user explicitly specifies the default value.

I would like to use argparse to process command line arguments, and I would like to use formatter_class=argparse.ArgumentDefaultsHelpFormatter to display what the default value will be for fields that are not specified. In addition, I would like to read values from a config file.

If the user specifies a value on the command line, I would like to make sure I use that value (even if that value matches the default, but was explicitly stated). If the user did not specify a value, but one is found in the config file, I would like to use that. If the user did not specify one on the command line, and there is not a value in the config file, then I would like to use the default value that was displayed in the usage statement above.

So, I might set up the parse like

parser = parser = argparse.ArgumentParser( description="""Tool with many ways to get values""", formatter_class=argparse.ArgumentDefaultsHelpFormatter )
parser.add_argument( '-p', '--path', help="The path to a file to read", default="data.csv" )
parser.add_argument( '-c', '--conf', help="The config file to use", default="config.txt" )

and perhaps there are many other parameters.

Now, I would like to also read a config file, which may include a value

data_path = data2.csv

So if the user specifies a -p on the command line, I would like to read that file; if they do not and I use that config file, I would like to read data2.csv; and if I use a config file that does not define data_path and they do not specify -p, I would like to use the default data.csv.

The main complicating case for me would be if the user specifies -p data.csv then it will have the value of the default value, but should take precedence over the config file.

Does argparse or another similar tool have a way to tell if the parameter was set by falling through to the default or was explicitly set by the user?

Jeff B
  • 8,572
  • 17
  • 61
  • 140
Eric Renouf
  • 13,950
  • 3
  • 45
  • 67
  • The parser maintains a `seen_actions` set to check, at the end, for required arguments. But that isn't available to you. – hpaulj Jun 06 '15 at 07:37
  • Related: [How to check whether optional function parameter is set](https://stackoverflow.com/q/14749328/3357935) – Stevoisiak Sep 20 '22 at 20:13

1 Answers1

7

Don't specify a default value if it's just complicating things:

parser.add_argument('-p', '--path', 
                    help="The path to a file to read")

And then later on in your code:

if args.path:
    # user specify a value for path
    # using -p
    pass
elif cfg.path:
    # user provided a path in the configuration value
    args.path = cfg.path
else:
    # no value was specified, use some sort of default value
    args.path = DEFAULT_PATH

Or, more compactly:

args.path = next(val for val in 
                 [args.path, cfg.path, DEFAULT_PATH]
                 if val is not None)

This assumes that cfg.path will be None if no path was specified in the config file. So if cfg is actually a dictionary, cfg.get('path') would do the right thing.

And just for kicks, here's a terrible idea that can tell the difference between using a default value and explicit specifying a value that is the same as the default:

import argparse

class Default(object):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return str(self.value)

DEFAULT_PATH = Default('/some/path')

def parse_args():
    p = argparse.ArgumentParser()
    p.add_argument('--path', '-p',
                   default=DEFAULT_PATH)
    return p.parse_args()

def main():
    args = parse_args()

    if args.path is DEFAULT_PATH:
        print 'USING DEFAULT'
    else:
        print 'USING EXPLICIT'

    print args.path

if __name__ == '__main__':
    main()

Note that I don't actually think this is a good idea.

larsks
  • 277,717
  • 41
  • 399
  • 399
  • 2
    The only reason I was shying away from that and hoping there was another way to tell is for formatting the usage statement. I can manually include the default in the `help` for the parameter, but it could be a pain making sure it always looks like other default values. – Eric Renouf Jun 06 '15 at 02:26
  • I've made a couple of updates, one possibly useful and one you should probably avoid. – larsks Jun 06 '15 at 02:32
  • Was going to say the same thing, without all the details, nice answer! – CMPSoares Jun 06 '15 at 02:41
  • A nice thing about `None` is that the user cannot supply it. – hpaulj Jun 06 '15 at 07:32
  • ...which gets you the first solution presented in this answer. – larsks Jun 06 '15 at 12:03
  • 1
    Why Default type is a bad idea? IMHO it works nicely and checking `if type(args.path) is Default: ...` is a nice and readable solution. Even is you have multiple arguments... What are the issues with it? – TrueY Feb 11 '22 at 09:13
  • Because in almost all cases I don't think it's necessary to differentiate between "user did not provide an argument" and "user provided an argument that matches the default value". – larsks Feb 11 '22 at 12:19