0

Using the very interesting answer provided here, I would like to be able to use argparse and execute multiple functions without having to define the arguments to each function. Otherwise the code is very overloaded or one has to multiply files to run different functions, which is not very practical either.

In the following example, the functions function1 and function2 have different arguments such as: function1(arg1: int, arg2: bool) and function2(arg3: float, arg4: str)

# file test.py
import argparse
from file1 import function1
from file2 import function2

FUNCTION_MAP = {'function1' : function1, 'function2': function2}

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

# no specific argument to add here. 

args = parser.parse_args()


func = FUNCTION_MAP[args.command]
func(**vars(args))  

The following commands with -- arguments do not work.

python test.py "function1" --arg1=10 --arg2=True

python test.py "function2" --arg3=2.4 --arg4="a_file.csv"

command as python test.py "function1" works but asks me the arguments of the function1.

Thanks for your help.

Tim
  • 513
  • 5
  • 20
  • You should use [subparsers](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_subparsers). See [this answer](https://stackoverflow.com/a/59087911/3279716), in the `run_command` function instead of printing you can get and call the correct function instead of using an `if/else` – Alex Aug 15 '21 at 14:12
  • How are you planning to get the values from the command line? I think you are overcomplicating your design here. At the moment you're heading towards using [`parser.parse_known_args()`](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.parse_known_args) to get the command and then re-parse the unspecified flags in your functions. Either way, you're going to have to specify your arguments and flags at some point. – Alex Aug 15 '21 at 14:27
  • Thanks for your suggestions (I edited the code). Knowing the arguments of the functions it's me ideally who defines the values in the command line. For example I could put `python test.py "function2" --arg3=3 --arg4='a.csv'` **without the need to put constraints on the arguments** with `parser.add_argument`. – Tim Aug 15 '21 at 14:36
  • The primary purpose of `argparse` is to parse arguments, not to execute commands. Its help and error checking are of little use if you don't define arguments. If you don't want those, parse the `sys.argv` list directly. (and save yourself the work of understanding `argparse`) – hpaulj Aug 15 '21 at 14:38
  • Who's `me`? The writer of the script, or the end user who calls the script? `argparse` gives the writer control, with usage guides and error checking. It's not intended for free form commandline input. – hpaulj Aug 15 '21 at 14:43
  • @hpaulj Thank you very much, yes I had already looked into `sys.argv` but argparse seemed to be a much more elegant solution, ideally `me` is the user intended to call the script. I could for example give it the following directly python `test.py "function2" --arg3=${float} --arg4=${csv file}`. The idea of the script in my question is to allow the user to directly execute a large number of methods with the arguments he wants and without having dozens of extra lines of code by defining the `parser.add_argument` for each function. I guess it's theoretically possible though? – Tim Aug 15 '21 at 14:53
  • All you are using `argparse` for is to grab `sys.argv[1]`. It does not provide any tools for converting `--arg3=${float}` – hpaulj Aug 15 '21 at 15:11
  • @hpaulj no sorry my bad, I have updated now with specific examples so that you can have a better idea. – Tim Aug 15 '21 at 15:14
  • In principle it's possible to create parsers on the fly that match a function signature but this would be quite precarious and would lead to a fair amount of code for handling edge cases. For example passing `--arg2=False` would result in `True` when parsing the string `False` with `bool` – Alex Aug 15 '21 at 15:33
  • 2
    There are third party parsers like https://pypi.org/project/plac/ that can create a parser that is customized for a set of functions like yours. It uses annotations to determine what kind of arguments your function(s) require, and defines `argparse` like arguments accordingly. There may be other packages like that, but `plac` is one that I used years ago. – hpaulj Aug 15 '21 at 17:09
  • @hpaulj sounds great, `plac` handles arguments without defining them but I didn't see in the doc if you can choose the function to call? I opened an issue [here](https://github.com/ialbert/plac/issues/60). – Tim Aug 16 '21 at 11:29

1 Answers1

0

If functions will be shuttling around/sharing lots of the same data, sounds like you want a class/object?

myobj.fn1() and myobj.fn2() will both have implicit access to the myobj object's data.

Use argparse input to define the initial instance via your class' __init__(self, x, y ...) method.

Adam Smooch
  • 1,167
  • 1
  • 12
  • 27
  • The two functions have **strictly** different arguments. So it would be awkward to share the same builder (__init__), right? – Tim Aug 15 '21 at 14:12
  • If the data is related, they could totally live in the same object, even if each fn only transacts on a subset. Or extend the idea to have 2 parallel classes if the data isn't related. – Adam Smooch Aug 15 '21 at 19:57