Use function/method defaults instead of argparse defaults

Viewed 50

When using argparse, I've not found an elegant/DRY way to use the function/method defaults instead of the defaults passed by argparse.

For example, I have foreign code I am loathe to modify. How do I tell argparse (or elegantly handle after argparse) to use the function defaults if the user does not pass in a clear preference on the command-line?

import argparse

def foreign_func(fav_color="blue"):
    print(fav_color)

clparser = argparse.ArgumentParser()
clparser.add_argument(
    "--color",
    default=None,
    help="Enter color")
clargs = clparser.parse_args()

foreign_func(fav_color=clargs.color)

This will print 'None', instead of "blue"

My default approach is something like:

if clparser.color:
    foreign_func(fav_color=clargs.color)
else:
    foreign_func()

but this seems clunky, especially if there are multiple command-line options.

EDIT: Hi folks, thank you for the fast feedback. To clarify, although it would be nice for argparse to display 'blue' in it's help, that's not my goal.

I'm looking for:

my_prog --color=red
>>> red

my_prog 
>>> blue

With the code above, the program is outputting 'None', not "blue"

3 Answers

You can use inspect to get the function's default parameter value, and use that as the default in the argparse code.

import argparse
import inspect

def foreign_func(fav_color="blue"):
    print(fav_color)

clparser = argparse.ArgumentParser()
clparser.add_argument(
    "--color",
    default=inspect.signature(foreign_func).parameters['fav_color'].default,
    help="Enter color")
clargs = clparser.parse_args()

foreign_func(fav_color=clargs.color)

Note that this will behave a bit oddly if there is no default in the function (if it's a required parameter). In that case, the default value will be the inspect._empty sentinel value, rather than an actually meaningful value. But your code seems to be assuming that the parameter is always optional, so I'm going with that too.

You can use a dictionary to specify your arguments by name; if a name is missing, that parameter will use its default. Example:

def func(a=1,b=2):
    print("a=",a,"b=",b)

func()
func(**{})
func(**{'a':10})

will print

a= 1 b= 2
a= 1 b= 2
a= 10 b= 2

Combining answers/tips from @Scott Hunter, @wim, and Adam Smith, the following solution seems workable.

This solution hinges on converting the argparse NameSpace object to a dict, then "splitting" that dict before passing to foreign_func. Along with the argparse.SUPPRESS option, the dict passed in will have no entry for missing command-line arguments. This avoids "stomping" on foreign_funcs defaults.

Note: I have not tested this approach in the case that foreign_func also has positional arguments, so YMMV.

import argparse

def foreign_func(fav_color="blue"):
    print(fav_color)

clparser = argparse.ArgumentParser()

clparser.add_argument(
    "--color",
    # Use 'dest' option if your command-line option name differs
    # from the function's argument name
    dest="fav_color",
    # 'argparse.SUPPRESS' will add an entry into the clargs "dict"
    # only if the option is passed on the command-line.
    default=argparse.SUPPRESS,
    help="Enter color")

clargs = clparser.parse_args()

print("DBG:", vars(clargs))
foreign_func(**vars(clargs))

Testing:

python my_prog.py --color=red
>>> red

python my_prog.py 
>>> blue
Related