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

PythonStringFunctionString Evaluation

Python Problem Overview


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?

Python Solutions


Solution 1 - Python

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.

Solution 2 - Python

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.'

Solution 3 - Python

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.

Solution 4 - Python

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)

Solution 5 - Python

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.

Solution 6 - Python

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.

Solution 7 - Python

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])

Solution 8 - Python

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

Solution 9 - Python

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)

Solution 10 - Python

[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()

Solution 11 - Python

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 ...

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBobView Question on Stackoverflow
Solution 1 - PythonunutbuView Answer on Stackoverflow
Solution 2 - PythonChris MorganView Answer on Stackoverflow
Solution 3 - PythonOscar KorzView Answer on Stackoverflow
Solution 4 - PythonFoo BahView Answer on Stackoverflow
Solution 5 - PythonJefferson FelixView Answer on Stackoverflow
Solution 6 - PythonHai VuView Answer on Stackoverflow
Solution 7 - PythonZurakinView Answer on Stackoverflow
Solution 8 - PythonsidarcyView Answer on Stackoverflow
Solution 9 - PythonMacGyverView Answer on Stackoverflow
Solution 10 - Pythonuser8651755View Answer on Stackoverflow
Solution 11 - PythonDavid EdwardsView Answer on Stackoverflow