How to pass several list of arguments to #click.option - python

I want to call a python script through the command line with this kind of parameter (list could be any size, eg with 3):
python test.py --option1 ["o11", "o12", "o13"] --option2 ["o21", "o22", "o23"]
using click. From the docs, it is not stated anywhere that we can use a list as parameter to #click.option
And when I try to do this:
#!/usr/bin/env python
import click
#click.command(context_settings=dict(help_option_names=['-h', '--help']))
#click.option('--option', default=[])
def do_stuff(option):
return
# do stuff
if __name__ == '__main__':
do_stuff()
in my test.py, by calling it from the command line:
python test.py --option ["some option", "some option 2"]
I get an error:
Error: Got unexpected extra argument (some option 2])
I can't really use variadic arguments as only 1 variadic arguments per command is allowed (http://click.pocoo.org/5/arguments/#variadic-arguments)
So if anyone can point me to the right direction (using click preferably) it would be very much appreciated.

If you don't insist on passing something that looks like a list, but simply want to pass multiple variadic arguments, you can use the multiple option.
From the click documentation
#click.command()
#click.option('--message', '-m', multiple=True)
def commit(message):
click.echo('\n'.join(message))
$ commit -m foo -m bar
foo
bar

You can coerce click into taking multiple list arguments, if the lists are formatted as a string literals of python lists by using a custom option class like:
Custom Class:
import click
import ast
class PythonLiteralOption(click.Option):
def type_cast_value(self, ctx, value):
try:
return ast.literal_eval(value)
except:
raise click.BadParameter(value)
This class will use Python's Abstract Syntax Tree module to parse the parameter as a python literal.
Custom Class Usage:
To use the custom class, pass the cls parameter to #click.option() decorator like:
#click.option('--option1', cls=PythonLiteralOption, default=[])
How does this work?
This works because click is a well designed OO framework. The #click.option() decorator usually instantiates a click.Option object but allows this behavior to be over ridden with the cls parameter. So it is a relatively easy matter to inherit from click.Option in our own class and over ride the desired methods.
In this case we over ride click.Option.type_cast_value() and then call ast.literal_eval() to parse the list.
Test Code:
#click.command(context_settings=dict(help_option_names=['-h', '--help']))
#click.option('--option1', cls=PythonLiteralOption, default=[])
#click.option('--option2', cls=PythonLiteralOption, default=[])
def cli(option1, option2):
click.echo("Option 1, type: {} value: {}".format(
type(option1), option1))
click.echo("Option 2, type: {} value: {}".format(
type(option2), option2))
# do stuff
if __name__ == '__main__':
import shlex
cli(shlex.split(
'''--option1 '["o11", "o12", "o13"]'
--option2 '["o21", "o22", "o23"]' '''))
Test Results:
Option 1, type: <type 'list'> value: ['o11', 'o12', 'o13']
Option 2, type: <type 'list'> value: ['o21', 'o22', 'o23']

The following can be an easier hack fix:
#!/usr/bin/env python
import click
import json
#click.command(context_settings=dict(help_option_names=['-h', '--help']))
#click.option('--option', help='Whatever')
def do_stuff(option):
try:
option = json.loads(option)
except ValueError:
pass
# do stuff
if __name__ == '__main__':
do_stuff()
This can help you to use 'option' as a list or a str.

#Murphy's "easy hack" almost worked for me, but the thing is that option will be a string unless you single quote the options, so I had to do this in order to recompose the list:
#!/usr/bin/env python
import click
import json
#click.command(context_settings=dict(help_option_names=['-h', '--help']))
#click.option('--option', help='Whatever')
def do_stuff(option):
try:
option = json.loads(option)
# option = str(option) # this also works
except ValueError:
pass
option = option[1:-1] # trim '[' and ']'
options = option.split(',')
for i, value in enumerate(options):
# catch integers
try:
int(value)
except ValueError:
options[i] = value
else:
options[i] = int(value)
# Here use options as you need
# do stuff
if __name__ == '__main__':
do_stuff()
You could catch some other types
To use it, enclose the list into quotes:
python test.py --option "[o11, o12, o13]"
Or you can avoid quotes by not leaving spaces:
python test.py --option [o11,o12,o13]

The answer of Stephen Rauch gave me some issues when working with Kubernetes arguments. This because the string formatting becomes a hassle and often new lines, single quotation marks or spaces were added before and after the array. Hence I made my own parser to improve this behaviour.
This code should be self explanatory.
Note that this code does not support single or double quotation marks ' or " in the variables itself.
Custom class:
class ConvertStrToList(click.Option):
def type_cast_value(self, ctx, value) -> List:
try:
value = str(value)
assert value.count('[') == 1 and value.count(']') == 1
list_as_str = value.replace('"', "'").split('[')[1].split(']')[0]
list_of_items = [item.strip().strip("'") for item in list_as_str.split(',')]
return list_of_items
except Exception:
raise click.BadParameter(value)
Custom class usage:
#click.option('--option1', cls=ConvertStrToList, default=[])

Click supports an option taking multiple arguments, as long as the number of arguments is predetermined. The arguments are separated by whitespace with no list-like syntax: one would write --my-2-arg-option arg1 arg2 rather than --my-2-arg-option ["arg1", "arg2"].
This response does answer your question about "how to pass several lists of arguments to #click.option," just that the lists need to be given without brackets or commas on the command line. A Python list could be formatted this way as follows, using shlex.join to add quotation marks when necessary:
>>> import shlex
>>> args = ["o11", "o12", "o13"]
>>> shlex.join(args)
'o11 o12 o13'
>>> args_needing_quotes = ["I have whitespace",
"$I_LOOK_LIKE_A_VARIABLE",
"$(i_have_special_characters)#!\n > *"]
>>> shlex.join(args_needing_quotes)
"'I have whitespace' '$I_LOOK_LIKE_A_VARIABLE' '$(i_have_special_characters)#!\n > *'"
Option that takes multiple arguments, all of the same type
This kind of option seems to be what you are looking for, and can be implemented with the following code (call it script1.py):
import click
#click.command()
#click.option("--option1", nargs=3)
def do_stuff(option1):
print("Option 1 is", option1)
do_stuff()
The parameter nargs tells Click how many arguments the option must accept; by default, nargs is 1.
Running this code prints the following output:
$ python script1.py --option1 four 4 IV
Option 1 is ('four', '4', 'IV')
We can see that the variable option1 is a tuple of three str values.
If fewer or more than 3 arguments are given for option1, then the script exits immediately:
$ python script1.py --option1 four 4
Error: Option '--option1' requires 3 arguments.
If option1 is omitted, then it defaults to None:
$ python script1.py
Option 1 is None
Default values of multi-argument options
A default value can be specified when the option is created. Note that the default value should also be a tuple of three str values to avoid any unexpected behavior. Particularly, it should not be a list (or any other mutable type), as mutable default arguments can cause unexpected behavior. Call the code with this change script2.py:
#click.option("--option1", nargs=3, default=('', '', ''))
Now the script prints the default when run with no arguments:
$ python script2.py
Option 1 is ('', '', '')
Types other than strings
Click will also automatically cast values to a different data type if given. Let script3.py be
import click
#click.command()
#click.option("--option1", nargs=3, type=float)
def do_stuff(option1):
print("Option 1 is", option1)
do_stuff()
Now option1 is a tuple of three float values:
$ python script3.py --option1 1 2.718 3.142
Option 1 is (1.0, 2.718, 3.142)
Option that takes multiple arguments of different data types
The previous examples showed how to create an option that takes multiple values of the same data type. What about an option that takes arguments of multiple data types?
Click provides a way to do this as well. Instead of setting nargs, set type to be a tuple of the desired data types. For example, let script4.py be
import click
#click.command()
#click.option("--comp", type=(float, str, float))
def calculate(comp):
num1, op, num2 = comp
if op == "+":
result = num1 + num2
elif op == "-":
result = num1 - num2
else:
raise ValueError(f"Unsupported operation: {op}")
print(f"{num1} {op} {num2} = {result}")
calculate()
Then we can use our rudimentary calculator:
$ python script4.py --comp 3 + 6
3.0 + 6.0 = 9.0
$ python script4.py --comp -1.2 - 3.7
-1.2 - 3.7 = -4.9
If any of the values are invalid, then click will raise an error automatically:
$ python script4.py --comp 1 + two
Usage: script4.py [OPTIONS]
Try 'script4.py --help' for help.
Error: Invalid value for '--comp': 'two' is not a valid float.
Option that can be given multiple times and takes multiple values
Click can also create options that can be given multiple times using the multiple keyword (script5.py):
import click
#click.command()
#click.option("--option1", nargs=2, multiple=True)
def do_stuff(option1):
print(option1)
do_stuff()
Now we see that option1 becomes a tuple of tuples of two strs each:
$ python script5.py --option1 3 a --option1 b 7
(('3', 'a'), ('b', '7'))
We can also mix multiple=True with the type keyword in script6.py:
import click
#click.command()
#click.option("--comps", type=(float, str, float), multiple=True)
def calculate(comps):
for comp in comps:
num1, op, num2 = comp
if op == "+":
result = num1 + num2
elif op == "-":
result = num1 - num2
else:
raise ValueError(f"Unsupported operation: {op}")
print(f"{num1} {op} {num2} = {result}")
calculate()
This functionality can allow us to, for example, code a simple calculator that performs multiple operations in one call to script6.py:
python script6.py --comps 4 - -7 --comps -8 + 4.2 --comps 16 - 34.1
4.0 - -7.0 = 11.0
-8.0 + 4.2 = -3.8
16.0 - 34.1 = -18.1
The full documentation for multi-value options in Click can be found here.
Note: I ran all code examples in Python 3.11.0 with Click 8.1.3

Related

Is there any way to use input for a mathematical function?

My numerical analysis professor gave me a project on programming several numerical methods in python.
And he asked me to run the program for some given functions so he can see the results of the methods.
My question is :
Is there any way so we can code something which the user input any chosen function and see the wanted results?
Or we must define the wanted function in the program specifically and there isn’t any way to do so ?
You can use a class to store your functions and access it by function name using getattr:
# Store your functions here
class Functions:
def f(x):
return x ** 2
def dist(a, b):
return pow(a**2 + b**2, .5)
def g(x):
return Functions.f(x) + 1
def main():
while True:
funcname = input('Insert the function name (type "exit" to stop): ')
if funcname == 'exit':
break
# Checks if the function name exists in Functions
if not hasattr(Functions, funcname):
print('This function does not exist!')
continue
# Store the function in a variable
func = getattr(Functions, funcname)
# Ask the user for the arguments to be used in the function
args = [float(arg) for arg in input('Insert the arguments (delimited by spaces): ').split()]
# Check if passed arguments match with function arguments
expected_args = func.__code__.co_argcount
actual_args = len(args)
if actual_args != expected_args:
print(f'Invalid number of arguments, expected {expected_args}, found {actual_args}')
continue
# Call the function with the provided arguments
print('The result is', func(*args))
if __name__ == '__main__':
main()
Usage example:
Insert the function name (type "exit" to stop): f
Insert the arguments (delimited by spaces): 10
The result is 100.0
Insert the function name (type "exit" to stop): dist
Insert the arguments (delimited by spaces): 3 4
The result is 5.0
Insert the function name (type "exit" to stop): g
Insert the arguments (delimited by spaces): 1
The result is 2.0
Insert the function name (type "exit" to stop): f
Insert the arguments (delimited by spaces): 1 2
Invalid number of arguments, expected 1, found 2
There is a way. By using regular expressions you can figure out what the function is by looking at the input string. İf you used any graphics calculater like desmos or geogebra, when you enter a mathematical expression as a string,the calculator will figure out what to calculate.
To parse math from a string input, you can use regular expressions.
If your question is how to run a specific function by calling it in a command-line input, you can store the functions in a separate module and use the inspect module to get references to these functions and their names.
import some_module
from inspect import getmembers, isfunction
functions = dict(getmembers(some_module, isfunction))
print("Available functions are: ", *functions.keys())
while True:
print("Type the name of the function you want to run and press enter.")
try:
functions[input()]()
except KeyError:
print("No such function found.")
>>> Available functions are: empty_function1 empty_function2
>>> Type the name of the function you want to run and press enter.
>>> Whatever
>>> No such function found.
>>> Type the name of the function you want to run and press enter.
>>> empty_function2
>>> Hello from the function 2!

Python parse numbers and options with getopt

I am trying to add command line options to program. The program should accept both a list of numbers, and parse options. For instance a call to the program could look like:
python3 ./parsetest.py 4 3 v=100
The 4 and the 3 will be processed in one way whereas the v=100 command will set the internal variable v to 100. I found the getopt library is supposed to have this functionality: http://stackabuse.com/command-line-arguments-in-python/ Is there a better/simpler way to do this?
When I try to parse the input like the above, I believe getopt should place arguments which trigger its keywords into a special list, and those which do not trigger are placed elsewhere. However, when I try to use getopt following the example above, no matter what I do, the v=100 option does not appear to trigger the special arguments list. When passed to getopt, I expected the 'v=100' string to be split up into argument v and value 100. For instance, the print(arguments,values) command below results in: [] ['4', '3', 'v=100']. There are values, but there are no arguments.
Here is my code:
import sys,getopt
v = 1e100
fullCmdArguments = sys.argv
argumentList = fullCmdArguments[1:]
unixOptions = "v:"
gnuOptions = ["v="]
print(argumentList)
try:
arguments, values = getopt.getopt(argumentList, unixOptions, gnuOptions)
except getopt.error as err:
# output error, and return with an error code
print (str(err))
sys.exit(2)
print(arguments, values)
for currentArgument, currentValue in arguments:
print (currentArgument, currentValue)
if currentArgument in ("-v", "v"):
v = currentValue
else:
#Its an integer then, do something with it
print()
If you can use argparse the following will work:
Example
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('numbers', type=int, nargs='+')
parser.add_argument('-v', type=int, default=-1)
args = parser.parse_args()
print(args.numbers)
print(args.v)
# due to the default set above, `args.v` will always have an int value
v = args.v
Using the nargs keyword argument you can set it to '+' requiring one or more numbers. The v with a hyphen is by default optional. If this is not wanted, you can add the required=True option.
Usage
python my_file.py 1 2 3 5 -v=12
[1, 2, 3, 5]
12
python my_file.py 1 2 3 5
[1, 2, 3, 5]
-1
Further reading
For more information about the argparse module you can have a look at:
The official tutorial: https://docs.python.org/3/howto/argparse.html
The module doc: https://docs.python.org/3/library/argparse.html#module-argparse

Type of the positional parameters in python

I'm quite new to python programming and I come from a Unix/Linux administration and shell scripting background. I'm trying to write a program in python which accepts command line arguments and depending on there type (int, str) performs certain action. However in my case the input is always being treated as string.Please advice.
#!/usr/bin/python
import os,sys,string
os.system('clear')
# function definition
def fun1(a):
it = type(1)
st = type('strg')
if type(a) == it:
c = a ** 3
print ("Cube of the give int value %d is %d" % (a,c))
elif type(a) == st:
b = a+'.'
c = b * 3
print ("Since given input is string %s ,the concatenated output is %s" % (a,c))
a=sys.argv[1]
fun1(a)
Command line arguments to Programs are always given as strings (this is not only true for python but at least all C-related languages). This means when you give a number like "1" as an argument, you need to explicitly convert it into an integer. In your case, you could try converting it and assume it is a string if this does not work:
try:
v = int(a)
#... do int related stuff
except ValueError:
#... do string related stuff
This is bad design though, it would be better to let the user decide if he wants the argument to be interpreted as a string - after all, every int given by the user is also a valid string. You could for example use something like argparse and specify two different arguments given with "-i" for int and "-s" for string.
First of all, the input will always be treated as string.
You could use argparse:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("cube", type=int,
help="Cube of the give int value ")
args = parser.parse_args()
answer = args.cube**3
print answer
python prog.py 4
64
All the integers have an attribute __int__, so you could use that attribute to differentiate between int and string.
if hasattr(intvalue, __int__):
print "Integer"
import argparse, ast
parser = argparse.ArgumentParser(description="Process a single item (int/str)")
parser.add_argument('item', type=ast.literal_eval,
help='item may be an int or a string')
item = parser.parse_args().item
if isinstance(item, int):
c = item ** 3
print("Cube of the give int value %d is %d" % (item,c))
elif isinstance(item, str):
b = item + '.'
c = b * 3
print("Since given input is string %s ,the concatenated output is %s"
% (item,c))
else:
pass # print error

Parse Args that aren't declared

I'm writing a utility for running bash commands that essentially takes as input a string and a list optional argument and uses the optional arguments to interpolate string.
I'd like it to work like this:
interpolate.py Hello {user_arg} my name is {computer_arg} %% --user_arg=john --computer_arg=hal
The %% is a separator, it separates the string to be interpolated from the arguments. What follows is the arguments used to interpolate the string. In this case the user has chosen user_arg and computer_arg as arguments. The program can't know in advance which argument names the user will choose.
My problem is how to parse the arguments? I can trivially split the input arguments on the separator but I can't figure out how to get optparse to just give the list of optional args as a dictionary, without specifying them in advance. Does anyone know how to do this without writing a lot of regex?
Well, if you use '--' to separate options from arguments instead of %%, optparse/argparse will just give you the arguments as a plain list (treating them as positional arguments instead of switched). After that it's not 'a lot of' regex, it's just a mere split:
for argument in args:
if not argument.startswith("--"):
# decide what to do in this case...
continue
arg_name, arg_value = argument.split("=", 1)
arg_name = arg_name[2:]
# use the argument any way you like
With argparse you could use the parse_known_args method to consume predefined arguments and any additional arguments. For example, using the following script
import sys
import argparse
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument('string', type=str, nargs='*',
help="""String to process. Optionally with interpolation
(explain this here...)""")
args, opt_args = parser.parse_known_args(argv)
print args
print opt_args
return 0
if __name__=='__main__':
sys.exit(main(sys.argv[1:]))
and calling with
python script.py Hello, my name is {name} --name=chris
yields the following output:
Namespace(string=['Hello,' 'my', 'name', 'is', '{name}'])
['--name=chris']
All that is left to do is to loop through the args namespace looking for strings of the form {...} and replacing them with the corresponding element in opt_args, if present. (I'm not sure if argparse can do argument interpolation automatically, the above example is the only immediate solution which comes to mind).
For something like this, you really don't need optparse or argparse - the benefit of such libraries are of little use in this circumstance (things like lone -v type arguments, checking for invalid options, value validation and so on)
def partition_list(lst, sep):
"""Slices a list in two, cutting on index matching "sep"
>>> partition_list(['a', 'b', 'c'], sep='b')
(['a'], ['c'])
"""
if sep in lst:
idx = lst.index(sep)
return (lst[:idx], lst[idx+1:])
else:
return (lst[:], )
def args_to_dict(args):
"""Crudely parses "--blah=123" type arguments into dict like
{'blah': '123'}
"""
ret = {}
for a in args:
key, _, value = a.partition("=")
key = key.replace("--", "", 1)
ret[key] = value
return ret
if __name__ == '__main__':
import sys
# Get stuff before/after the "%%" separator
string, args = partition_list(sys.argv[1:], "%%")
# Join input string
string_joined = " ".join(string)
# Parse --args=stuff
d = args_to_dict(args)
# Do string-interpolation
print string_joined.format(**d)

I have a string whose content is a function name, how to refer to the corresponding function in Python?

For example, if I have a function called add like
def add(x,y):
return x+y
and I want the ability to convert a string or an input to direct to that function like
w=raw_input('Please input the function you want to use')
or
w='add'
Is there any way to use w to refer to the function add?
Since you are taking user input, the safest way is to define exactly what is valid input:
dispatcher={'add':add}
w='add'
try:
function=dispatcher[w]
except KeyError:
raise ValueError('invalid input')
If you want to evaluate strings like 'add(3,4)', you could use safe eval:
eval('add(3,4)',{'__builtins__':None},dispatcher)
eval in general could be dangerous when applied to user input. The above is safer since __builtins__ is disabled and locals is restricted to dispatcher. Someone cleverer than I might be able to still cause trouble, but I couldn't tell you how to do it.
WARNING: Even eval(..., {'__builtins__':None}, dispatcher) is unsafe to be applied to user input. A malicious user could run arbitrary functions on your machine if given the opportunity to have his string evaluated by eval.
One safe way is to map from names to functions. It's safer than using eval.
function_mappings = {
'add': add,
}
def select_function():
while True:
try:
return function_mappings[raw_input('Please input the function you want to use')]
except KeyError:
print 'Invalid function, try again.'
The built-in function eval will do what you want. All the usual warnings about executing arbitrary user-supplied code apply.
If there are a finite number of predefined functions, you should avoid eval and use a lookup table instead (i.e. Dict). Never trust your users.
unutbu's solution is what I would normally use, but for completeness sake:
If you are specifying the exact name of the function, you can use eval, although it is highly discouraged because people can do malicious things:
eval("add")(x,y)
Just use function reference:
def pwr(x, y):
return x ** y
def add(x, y):
return x + y
dispatcher = { 'pwr' : pwr, 'add' : add}
def call_func(x, y, func):
try:
return dispatcher[func](x, y)
except:
return "Invalid function"
call_func(2, 3, 'add')
Simple and secure.
If you are implementing a shell-like application where the user enter some command (such as add), and the application responses (return the sum), you can use the cmd module, which handles all the command interactions and dispatching for you. Here is an example:
#!/usr/bin/env python
import cmd
import shlex
import sys
class MyCmd(cmd.Cmd):
def do_add(self, arguments):
'''add - Adds two numbers the print the sum'''
x, y = shlex.split(arguments)
x, y = int(x), int(y)
print x + y
def do_quit(self, s):
'''quit - quit the program'''
sys.exit(0)
if __name__ == '__main__':
cmd = MyCmd()
cmd.cmdloop('type help for a list of valid commands')
Here is a sample running session:
$ python cmd_tryout.py
type help for a list of valid commands
(Cmd) help add
add - Adds two numbers the print the sum
(Cmd) add 5 3
8
(Cmd) quit
At the prompt (Cmd), you can issue the help command which you get for free. Other commands are add and quit which correspond to the do_add() and do_quit() functions.
Note that help command displays the docstring for your function. The docstring is a string immediately follows the function declararation (see do_add() for example).
The cmd module does not do any argument spliting, parsing, so you have to do it yourself. The do_add() function illustrates this.
This sample program should be enough to get you started. For more information look up the cmd help page. It is trivia to customize the prompt and other aspect of your program.
I had the same problem.
The way I recommend you to handle it is to create a temporary Python file to store the function the user input. Here's an example I used in a program I wrote to draw representations of mathematical functions:
with open("function.py",'w') as file:
f=input('enter the function you want to draw example: 2*x+1 or e**x :\n')
file.write("from math import *\ndef f(x):\n\treturn "+f)
This will create a file containing the function I want to call.
Next, you must call the function you wrote in the file to your program:
from function import f
Now you can use your function as normal python function.
If you want, you can also delete the file where you stored your function using os.remove:
import os
os.remove("function.py")
To help you understand, here is my program to draw mathematical functions:
import numpy
import cv2
import os
from math import *
def generate(f,a,b,min,max,functionname='noname'):
ph=(b-a)/1920
pv=(max-min)/1080
picture=numpy.zeros((1080,1920))
for i in range(0,1920):
picture[1079-(int((f(a+(i+1)*ph)*1080/max))),i]=255
for i in range(1920):
picture[1079-(int((f(a+(i+1)*ph)*1080/max)))+1,i]=255
cv2.imwrite(functionname+'.png',picture)
with open("function.py",'w') as file:
f=input('enter the function you want to draw example: or e**x :\n')
file.write("from math import *\ndef f(x):\n\treturn "+f)
from function import f
os.remove("function.py")
d=input('enter the interval ,min ,max and the image file name. Separate characters with spacebar. Example: 0 1 0 14 exponontielle :\n').split(" ")
generate(f,int(d[0]),int(d[1]),int(d[2]),int(d[3]),d[4])
def add(x,y):
print(x+y)
def subtract(x,y):
print(x-y)
function_list = {'add', 'subtract'}
def caller(func, x, y):
eval(func)(x,y) # more security exploits
if func in function_list:
eval(func)(x,y) # less security exploits
caller("add", 1, 2)
I've had many situation where I've needed to compare a string to an int and vice versa within a Django template.
I created a filter that allowed me to pass in the function name and using eval() convert it.
Example:
Template:
{% ifequal string int|convert:'str' %} do something {% endifequal %}
Template Filter (where i use a string to call the function name):
#register.filter
def convert(value, funcname):
try:
converted = eval(funcname)(value)
return converted
except:
return value
With reference to John Curry's question above ... if you want a version of Jefferson Felix's code that handles multiple arguments, then the simplest solution is to provide the arguments in a list, and arrange for each of the dispatched functions to check the argument count before proceeding.
A simple version that I've just tested in Visual Studio code is as follows:
import math
def sin(args):
argc = len(args)
if (argc == 1):
result = math.sin(args[0])
else:
result = None
return(result)
def sum(args):
argc = len(args)
if (argc == 2):
result = args[0] + args[1]
else:
result = None
return(result)
def dot_product(args):
argc = len(args)
if (argc == 2):
vector1 = args[0]
vector2 = args[1]
if (len(vector1) == 3 and len(vector2) == 3):
result = (vector1[0] * vector2[0]) + (vector1[1] * vector2[1]) + (vector1[2] * vector2[2])
else:
result = None
else:
result = None
return(result)
dispatcher = {"sin" : sin, "sum" : sum, "dot_product" : dot_product}
def call_func(dispatcher, func_name, args):
func_list = list(dispatcher.keys())
if (func_list.count(func_name) == 0):
return(None)
else:
return(dispatcher[func_name](args))
val = call_func(dispatcher, "sin", [0.6])
print(f"Sine is : {val}")
val = call_func(dispatcher, "sum", [4, 6])
print(f"sum is : {val}")
val = call_func(dispatcher, "dot_product", [[3, 7, 2], [5, 9, 4]])
print(f"dot product is : {val}")
The output looks as follows:
Sine is : 0.5646424733950354
sum is : 10
dot product is : 86
Of course, a more sophisticated version would include better error trapping that simply returning "None" if an error is found, but the above can be used as a template to build upon. Likewise, the dot_product function could be improved to handle vectors of any dimension, but I leave that as an exercise for the reader ...
[I got here via a duplicate question. My first thought was to use argparse and shlex and I didn't see that here, so I'm adding it as another option.]
You could use argparse to set up a registry of functions/commands and safely parse their args. This will provide some level of user-friendliness too by, e.g., letting you know when you've entered a command that doesn't exist.
import argparse
import shlex
def hello(name):
print('hello,', name)
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
hello_parser = subparsers.add_parser('hello')
hello_parser.add_argument('name')
hello_parser.set_defaults(func=hello)
print('Enter q to quit')
while True:
command = input('command> ')
command = command.strip()
if not command:
continue
if command.lower() == 'q':
break
words = shlex.split(command)
try:
args = parser.parse_args(words)
except SystemExit:
# argparse will sys.exit() on -h and errors; prevent that
continue
func_args = {name: value for name, value in vars(args).items()}
del func_args['func']
args.func(**func_args)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print()
This question belongs to an arcane Python era obviously, but I thought this might be useful for people getting here from search engines..
Nowadays, you can do something like this (no manual mappings required):
func = locals()[ name ]
print( f'Found local function {func}' )
func()
Of course, for any real use case you should check that you actually found anything before executing it!

Categories