kcw78
kcw78

Reputation: 8081

Argparse value for optional positional argument

I am trying to create an argparse optional argument that may -OR- may not have an associated input value. I want the following behavior:
1. argument not specified, value=None
2. argument specified with a value, value=user_input
3. argument specified without a value, value=derived from a positional value

The first 2 are easy. It's the third one I can't figure out. I found 2 posts that do something similar:
python argparse optional positional argument with detectable switch
This one sets the default value to a constant for an optional argument without a value.
Argparse optional positional arguments?
This topic is close, but not quite what I need either (he derives the default value from a system call):
I want mine to be a determined from an positional value.

Simple example code I created:

parser = argparse.ArgumentParser()
parser.add_argument('input')
parser.add_argument('-c', '--csv', nargs='?')
parser.add_argument('-p', '--pnf', nargs='?')

When I set the input and print:

args = parser.parse_args('my.h5 -c my_file.csv --pnf'.split())
print ('Input = %s' % args.input)
print ('CSV file = %s' % args.csv)
print ('PNF file = %s' % args.pnf)

I get:

Input = my.h5
CSV file = my_file.csv
PNF file = None

If I modify my input to:

args = parser.parse_args('my.h5 -c'.split())

The resulting output is:

Input = my.h5
CSV file = None
PNF file = None

When Value = None, I can't tell if the optional argument was not defined, or was defined but without a value. In the second case, I want to derive the CSV File name from the positional argument (in this example the derived name would be my.csv). I want to do the same when --pnf is defined (where default PNF would be my.pnf for above). Is there a way to do this?

Upvotes: 1

Views: 3472

Answers (4)

wim
wim

Reputation: 363456

I can't tell if the optional argument was not defined, or was defined but without a value

If you create your parser like this:

parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)

Now it is possible to distinguish between the three cases.

  • If -c val was passed, it will be present in args with value "val".
  • If -c was passed without value, it will be present in args with value None.
  • If -c was omitted entirely, it won't be present in args.

The same goes for -p.

If you only want this suppression to apply for one option, rather than on the whole parser, that is possible too:

parser.add_argument('-c', '--csv', nargs='?', default=argparse.SUPPRESS)

Upvotes: 3

kcw78
kcw78

Reputation: 8081

This shows the logic I implemented using default=False, const=True, based on earlier comments from @hpaulj.

parser = argparse.ArgumentParser()
parser.add_argument('input')
parser.add_argument('-c', '--csv', nargs='?', default=False, const=True)
parser.add_argument('-p', '--pnf', nargs='?', default=False, const=True)
args = parser.parse_args('my.h5 -c'.split())
print(vars(args))
HDF5_FILE = args.input
if isinstance(args.csv, str) :
  CSV_FILE = args.csv
elif args.csv :
  CSV_FILE=HDF5_FILE[:-3] + '_v3_stress.csv'
else :
  CSV_FILE = ''
# repeat above logic for args.pnf
print ('input=', HDF5_FILE, ',  csv=', CSV_FILE, ' pnf=', PNF_FILE )

Resulting output looks like this:

{'input': 'my.h5', 'csv': True, 'pnf': False}
hdf5= my.h5 ,  csv= my_v3_stress.csv  pnf= 

Modified 'parse_args()' and resulting output:

args = parser.parse_args('my.h5 -c my_file.csv -p'.split())

Gives:

{'input': 'my.h5', 'csv': 'my_file.csv', 'pnf': True}
hdf5= my.h5 ,  csv= my_file.csv  pnf= my_v3_stress.nrf

If I had a lot of variables to check, I would move the if/elif/else logic to a def that returns the desired value.

Upvotes: 0

kcw78
kcw78

Reputation: 8081

For completeness (and future reference), I am posting the modified code required to get arguments when using argument_default=argparse.SUPPRESS. See below:

parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)
parser.add_argument('input')
parser.add_argument('-c', '--csv', nargs='?')
parser.add_argument('-p', '--pnf', nargs='?')
args = parser.parse_args('my.h5 -c my_file --pnf'.split())
for d_key, d_val in vars(args).items() :
  print (d_key, d_val)

Results in this output:

input my.h5
csv my_file
pnf None

For the second set of inputs

args = parser.parse_args('my.h5 -c'.split())
print(vars(args))
for d_key, d_val in vars(args).items() :
  print (d_key, d_val)

Output looks like this:

input my.h5
csv None

Upvotes: 0

hpaulj
hpaulj

Reputation: 231665

Making use of the const parameter:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('input')
parser.add_argument('-c', '--csv', nargs='?', const='foobar')
parser.add_argument('-p', '--pnf', nargs='?', const='foobar')

args = parser.parse_args()
print(args)

if args.csv and args.csv=='foobar':
    args.csv = args.input

args.pnf = args.input if (args.pnf and args.pnf=='foobar') else args.pnf

print(args)

Your two sample inputs:

0933:~/mypy$ python3 stack53228663.py my.h5 -c my_file.csv --pnf
Namespace(csv='my_file.csv', input='my.h5', pnf='foobar')
Namespace(csv='my_file.csv', input='my.h5', pnf='my.h5')

0933:~/mypy$ python3 stack53228663.py my.h5 -c
Namespace(csv='foobar', input='my.h5', pnf=None)
Namespace(csv='my.h5', input='my.h5', pnf=None)

Upvotes: 0

Related Questions