-3

I would like to call a function from a user input, but include arguments in the parenthesis. For example, if I have a function that takes one argument:

def var(value):
    print(value)

I would like to ask the user for a command and arguments, then call the function with the arguments:

Input Command: var("Test")
Test
davidism
  • 121,510
  • 29
  • 395
  • 339
MasterHolbytla
  • 177
  • 1
  • 2
  • 11

5 Answers5

9

Split the function name from the arguments. Look up the function by name using a predefined map. Parse the arguments with literal_eval. Call the function with the arguments.

available = {}

def register_func(f):
    available[f.__name__] = f

@register_func
def var(value):
    print(value)

from ast import literal_eval

def do_user_func(user_input):
    name, args = user_input.split('(', 1)
    return available[name](*literal_eval('(' + args[:-1] + ',)'))

do_user_func("var('test')")  # prints "test"

This is still incredibly brittle, any invalid input will fail (such as forgetting parentheses, or an invalid function name). It's up to you to make this more robust.

literal_eval is still somewhat unsafe on untrusted input, as it's possible to construct small strings that evaluate to large amounts of memory. '[' * 10 + ']' * 10, for a safe but demonstrative example.


Finally, do not use eval on untrusted user input. There is no practical way to secure it from malicious input. While it will evaluate the nice input you expect, it will also evaluate code that, for example, will delete all your files.

Any attempt to make eval safe will end up being more complex than any of the solutions here, for no practical benefit. It will still not be safe in some way you didn't anticipate. Don't do it.

davidism
  • 121,510
  • 29
  • 395
  • 339
3

I am going to post this solution as an alternative, under the assumption that you are dealing with simple inputs such as:

var(arg)

Or, a single function call that can take a list of positional arguments.

By using eval it would be a horrible un-recommended idea, as already mentioned. I think that is the security risk you were reading about.

The ideal way to perform this approach is to have a dictionary, mapping the string to the method you want to execute.

Furthermore, you can consider an alternative way to do this. Have a space separated input to know how to call your function with arguments. Consider an input like this:

"var arg1 arg2"

So when you input that:

call = input().split()

You will now have:

['var', 'arg1', 'arg2']

You can now consider your first argument the function, and everything else the arguments you are passing to the function. So, as a functional example:

def var(some_arg, other_arg):
    print(some_arg)
    print(other_arg)

d = {"var": var}

call = input().split()

d[call[0]](*call[1:])

Demo:

var foo bar
foo
bar
idjaw
  • 25,487
  • 7
  • 64
  • 83
2

You should investigate the cmd module. This allows you to parse input similar to shell commands, but I believe you can get tricky and change the delimiters if the parentheses are an important part of the specification.

holdenweb
  • 33,305
  • 7
  • 57
  • 77
0

Instead of using eval, you can parse it yourself. This way, you have control over how each function should parse/deserialize the user input's arguments.

import sys, re

def custom_print(value):
    print value

def custom_add(addends):
    print sum(addends)

def deserialize_print(args):
    # just print it as is
    custom_print(args)

def deserialize_add(args):
    # remove all whitespace, split on commas, parse as floats
    addends = [float(x) for x in re.sub(r"\s", "", args).split(",")]
    # send to custom_add function
    custom_add(addends)

def get_command():
    cmd_input = raw_input("Command: ")
    # -- check that the command is formatted properly
    #    and capture command groups
    match = re.match(r"^([a-zA-Z0-9]+)(\(.*\))?$", cmd_input)
    if match:
        # extract matched groups to separate variables
        (cmd, argstring) = match.groups()

        # strip parenthesis off of argstring
        if argstring:
            args = argstring[1:-1]

        # send the whole argument string to its corresponding function
        if cmd == "print":
            deserialize_print(args)
        elif cmd == "add":
            deserialize_add(args)
        elif cmd == "exit":
            sys.exit()
        else:
            print "Command doesn't exist."
    else:
        print "Invalid command."

    # recurse until exit
    get_command()


# -- begin fetching commands
get_command()

This is a pretty rough setup, although you can get by with some more error checking and improving the deserializing functions and modularizing function additions.

If the decoupled deserialize functions seem too much, you can also just move the deserialization into the custom functions themselves.

Jerome Indefenzo
  • 967
  • 6
  • 25
0

Following is an example of function called from user-input, using Class:

class Wash:
       
def __init__(self, amount): 
    self.amount = amount
    
    if amount == 12:
        print("Platinum Wash")

    elif amount == 6:
        print("Basic Wash")

    else:
        print("Sorry!")

amount = int(input("Enter amount: "))

payment = Wash(amount)