Question

I need to write a piece of software that takes a user-defined function (written in python) as an input.

The user-defined function takes a vector of numbers and returns a vector of numbers. My software will call this user function many times (in the same way a root search would do) and then returns some result.

The source code of my soft will be written in python (it will use a *.pyd) or in C++ and must be hidden from the user.

What is the best way (if there is any...) to achieve this? Ideally I would have my python code compiled in *.exe and the user would copy-paste his/her function in a text box, but using it from python interpreter should be acceptable, too.

Was it helpful?

Solution

Here's a very limited example which shows how you could do it -- Of course, there are some limitations here -- Mainly, this only works if the user only inputs a single function. If the string they write looks more like:

a='garbage'
def foo():pass

or even:

def bar():
    return foobar()

def foobar():
    return "foobar is a cool word, don't you think?"

then you're out of luck. (In other words, this assumes that the user is only adding one thing to the namespace of the run_user function). Of course, you could check that and raise an exception or whatever if it turns out the user added too much...You could also return the function and use it as proposed by gauden.

def run_user(S):
    #S is the user's function as a string.
    lvars=None #make sure the name is in locals()
    lvars=set(locals())
    exec(S)  #exec isn't usually a good idea -- but I guess you're a very trusting person.
    usr_namespace=list(set(locals())-lvars)
    usr_func_name=usr_namespace[0]
    if(len(usr_namespace)>1):
        raise ValueError("User input too much into the namespace!")

    usr_func=locals()[usr_func_name]
    usr_func()  #comment this out if you don't want to run the function immediately
    return usr_func

usr_string="""
def foo():
     a="Blah"
     print "Hello World! "+a
"""

func_handle=run_user(usr_string)  #prints "Hello World! Blah"
#and to demonstrate that we can pass a handle to the function around:...
func_handle() #prints "Hello World! Blah" again.  

Note that you could do this a little more safely using python 3's exec or python 2's execfile where you could limit the namespace of the user's function by passing the dictionary {'__builtins__':None} as the global dictionary

#python3.x
allowed=vars(__builtins__).copy()
allowed['__import__']=None
exec("import os",{'__builtins__':None},allowed)  #raises ImportError
exec("print(abs(-4))",{'__builtins__':None},allowed) #prints 4 as you'd expect.

I would expect the same thing to work with execfile under python2.x provided you wrote the string to a temporary file...

EDIT (to address the comments below)

The example that you provide with eval could be done a little more simply:

a=5
b=eval('a+5')  #b == 10

However, this isn't what you asked for. What you asked for was that the user could write a function, e.g.:

def f(a):
    return a+5

The former case will work, but the user needs to know that the variable name is 'a'.

a=5
b=eval('x+5') #won't work -- x isn't defined

They also need to know how to add the vectors -- (If you're using numpy arrays that's trivial, but I thought I would mention it just in case you're not). And, they can't make complex expressions (long expressions using multiple conditionals, loops, etc.) without a decent amount of work and head-scratching.

The latter case is a little better (in my opinion) because it is much more general. You can get the function using the method I described (removing the part in there where I actually run the function) and the user can use any variable name(s) they want -- then you just use their function. They can also do things like loops, and use expressions that are much more complex than you could do in a single line with eval. The only thing you pay for this is that the user needs to write def func(...): and return some_value at the end of it which if they know python should be completely intuitive.

ss="""
def foo(x):
    return 5+x
"""

a=5
func=run_user(ss)
result=func(a)     #result = 10

This also has the advantage that the string doesn't need to be re-parsed every time you want to call the function. Once you have func, you can use it however/whenever you want. Also note that with my solution, you don't even need to know the name of the function the user defined. Once you have the function object, the name is irrelevant.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top