Call function based on argparse

Question:

I’m new to python and currently playing with it.
I have a script which does some API Calls to an appliance. I would like to extend the functionality and call different functions based on the arguments given when calling the script.

Currently I have the following:

parser = argparse.ArgumentParser()
parser.add_argument("--showtop20", help="list top 20 by app",
                    action="store_true")
parser.add_argument("--listapps", help="list all available apps",
                    action="store_true")
args = parser.parse_args()

I also have a

def showtop20():
    .....

and

def listapps():
....

How can I call the function (and only this) based on the argument given?
I don’t want to run

if args.showtop20:
   #code here

if args.listapps:
   #code here

as I want to move the different functions to a module later on keeping the main executable file clean and tidy.

Asked By: f0rd42

||

Answers:

If your functions are “simple enough” take adventage of type parameter https://docs.python.org/2.7/library/argparse.html#type

type= can take any callable that takes a single string argument and
returns the converted value:

In your example (even if you don’t need a converted value):

parser.add_argument("--listapps", help="list all available apps",
                    type=showtop20,
                    action="store")

This simple script:

import argparse

def showtop20(dummy):
    print "{0}n".format(dummy) * 5

parser = argparse.ArgumentParser()
parser.add_argument("--listapps", help="list all available apps",
                    type=showtop20,
                    action="store")
args = parser.parse_args()

Will give:

# ./test.py --listapps test
test
test
test
test
test
test

Since it seems like you want to run one, and only one, function depending on the arguments given, I would suggest you use a mandatory positional argument ./prog command, instead of optional arguments (./prog --command1 or ./prog --command2).

so, something like this should do it:

FUNCTION_MAP = {'top20' : my_top20_func,
                'listapps' : my_listapps_func }

parser.add_argument('command', choices=FUNCTION_MAP.keys())

args = parser.parse_args()

func = FUNCTION_MAP[args.command]
func()
Answered By: Hannes Ovrén

There are lots of ways of skinning this cat. Here’s one using action='store_const' (inspired by the documented subparser example):

p=argparse.ArgumentParser()
p.add_argument('--cmd1', action='store_const', const=lambda:'cmd1', dest='cmd')
p.add_argument('--cmd2', action='store_const', const=lambda:'cmd2', dest='cmd')

args = p.parse_args(['--cmd1'])
# Out[21]: Namespace(cmd=<function <lambda> at 0x9abf994>)

p.parse_args(['--cmd2']).cmd()
# Out[19]: 'cmd2'
p.parse_args(['--cmd1']).cmd()
# Out[20]: 'cmd1'

With a shared dest, each action puts its function (const) in the same Namespace attribute. The function is invoked by args.cmd().

And as in the documented subparsers example, those functions could be written so as to use other values from Namespace.

args = parse_args()
args.cmd(args)

For sake of comparison, here’s the equivalent subparsers case:

p = argparse.ArgumentParser()
sp = p.add_subparsers(dest='cmdstr')
sp1 = sp.add_parser('cmd1')
sp1.set_defaults(cmd=lambda:'cmd1')
sp2 = sp.add_parser('cmd2')
sp2.set_defaults(cmd=lambda:'cmd2')

p.parse_args(['cmd1']).cmd()
# Out[25]: 'cmd1'

As illustrated in the documentation, subparsers lets you define different parameter arguments for each of the commands.

And of course all of these add argument or parser statements could be created in a loop over some list or dictionary that pairs a key with a function.

Another important consideration – what kind of usage and help do you want? The different approaches generate very different help messages.

Answered By: hpaulj

At least from what you have described, --showtop20 and --listapps sound more like sub-commands than options. Assuming this is the case, we can use subparsers to achieve your desired result. Here is a proof of concept:

import argparse
import sys

def showtop20():
    print('running showtop20')

def listapps():
    print('running listapps')

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

# Create a showtop20 subcommand    
parser_showtop20 = subparsers.add_parser('showtop20', help='list top 20 by app')
parser_showtop20.set_defaults(func=showtop20)

# Create a listapps subcommand       
parser_listapps = subparsers.add_parser('listapps', help='list all available apps')
parser_listapps.set_defaults(func=listapps)

# Print usage message if no args are supplied.

# NOTE: Python 2 will error 'too few arguments' if no subcommand is supplied.
#       No such error occurs in Python 3, which makes it feasible to check
#       whether a subcommand was provided (displaying a help message if not).
#       argparse internals vary significantly over the major versions, so it's
#       much easier to just override the args passed to it.

if len(sys.argv) <= 1:
    sys.argv.append('--help')

options = parser.parse_args()

# Run the appropriate function (in this case showtop20 or listapps)
options.func()

# If you add command-line options, consider passing them to the function,
# e.g. `options.func(options)`
Answered By: Six

Instead of using your code as your_script --showtop20, make it into a sub-command your_script showtop20 and use the click library instead of argparse. You define functions that are the name of your subcommand and use decorators to specify the arguments:

import click

@click.group()
@click.option('--debug/--no-debug', default=False)
def cli(debug):
    print(f'Debug mode is {"on" if debug else "off"}')

@cli.command()  # @cli, not @click!
def showtop20():
    # ...

@cli.command()
def listapps():
    # ...

See https://click.palletsprojects.com/en/master/commands/

Answered By: Boris Verkhovskiy
# based on parser input to invoke either regression/classification plus other params

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--path", type=str)
parser.add_argument("--target", type=str)
parser.add_argument("--type", type=str)
parser.add_argument("--deviceType", type=str)    

args = parser.parse_args()
df = pd.read_csv(args.path)
df = df.loc[:, ~df.columns.str.contains('^Unnamed')]
if args.type == "classification":
    classify = AutoML(df, args.target, args.type, args.deviceType)
    classify.class_dist()
    classify.classification()

elif args.type == "regression":
    reg = AutoML(df, args.target, args.type, args.deviceType)
    reg.regression()

else:
    ValueError("Invalid argument passed")


# Values passed as : python app.py --path C:UsersAbhishekDownloadsadult.csv --target income --type classification --deviceType GPU

You can evaluate using evalwhether your argument value is callable:

import argparse

def list_showtop20():
    print("Calling from showtop20")
def list_apps():
    print("Calling from listapps")

my_funcs = [x for x in dir() if x.startswith('list_')]

parser = argparse.ArgumentParser()
parser.add_argument("-f", "--function", required=True,
                        choices=my_funcs,
                        help="function to call", metavar="")

args = parser.parse_args()

eval(args.function)()
Answered By: Bart
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.