how to make argument optional in python argparse

Question:

I would like to make these invocations of myprog work, and no others.

$ python3 myprog.py -i infile -o outfile
$ python3 myprog.py -o outfile
$ python3 myprog.py -o
$ python3 myprog.py 

In particular I want to make it illegal to specify the infile but not the outfile.

In the third case, a default name for the outfile is assumed, “out.json.” In the second, third and fourth cases, a default name for the input file is assumed, “file.n.json”, where n is an integer version number. In the fourth case the output file would be “file.n+1.json” where n+1 is a version number one larger than the one on the input file. The relevant section of my code is:

import argparse

parser = argparse.ArgumentParser(description="first python version")
parser.add_argument('-i', '--infile', nargs=1, type=argparse.FileType('r'), help='input file, in JSON format')
parser.add_argument('-o', '--outfile', nargs='?', type=argparse.FileType('w'), default='out.json', help='output file, in JSON format')

args = parser.parse_args()

print("Here's what we saw on the command line: ")
print("args.infile",args.infile)
print("args.outfile",args.outfile)

if args.infile and not args.outfile:
    parser.error("dont specify an infile without specifying an outfile")
elif not args.infile:
    print("fetching infile")
else: # neither was specified on the command line
    print("fetching both infile and outfile")

Problem is, when I run

$ python3 myprog.py -i infile.json

instead of the parser error I hoped for, I get:

Here's what we saw on the command line: 
args.infile [<_io.TextIOWrapper name='infile.json' mode='r' encoding='UTF-8'>]
args.outfile <_io.TextIOWrapper name='out.json' mode='w' encoding='UTF-8'>
fetching both infile and outfile

…which suggests that even though there was no “-o” on the command line it acted as if there was.

Asked By: user1416227

||

Answers:

You specified a default argument for the outfile.

parser.add_argument('-o', '--outfile', nargs='?', type=argparse.FileType('w'), default='out.json', help='output file, in JSON format')

If the -o option isn’t specified at the command line, the arg parser inserts the default argument.

Change this to:

parser.add_argument('-o', '--outfile', nargs='?', type=argparse.FileType('w'), help='output file, in JSON format')

and things should work as you expect.


If you want to be able to specify -o, without a filename, you probably want something like:

out_file = args.out if args.out is not None else 'json.out'

I’m not sure if the relevant parameter will be None or '' (i.e., empty string) if you specify the -o without a parameter–I suspect it’s None, but I don’t know for sure. You’ll have to test it out to be sure.

I don’t know how to do this without extra logic with argparse.

Answered By: BenDundee

As an add-on to the selected answer:

The option to run -o without specifying a file, can be done using const combined with nargs='?'.

From the docs:

When add_argument() is called with option strings (like -f or –foo)
and nargs=’?’. This creates an optional argument that can be followed
by zero or one command-line arguments. When parsing the command line,
if the option string is encountered with no command-line argument
following it, the value of const will be assumed instead. See the
nargs description for examples.

Example (with type string):

parser.add_argument('-o', '--outfile', nargs='?', const='arg_was_not_given', help='output file, in JSON format')

args = parser.parse_args()

if args.outfile is None:
    print('Option not given at all')
elif args.outfile == 'arg_was_not_given':
    print('Option given, but no command-line argument: "-o"')
elif:
    print('Option and command-line argument given: "-o <file>"')
Answered By: mhristache
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.