2

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.

Yulia V
  • 3,507
  • 10
  • 31
  • 64
  • 1
    So you trust your users so much that you will run arbitrary code from them, but you distrust them so much that your code needs to be completely inaccessible? – Steven Rumbalski Jun 01 '12 at 12:26
  • @Levon: the obvious solution is to create a micro python interpreter, which should be doable because the user function will contain assgnments, arthmetic operations, comparisons and "for" loops only (or so I hope); but this is a very time-consuming and awkward solution. I hope to be able to get something better from python. – Yulia V Jun 01 '12 at 12:33
  • @Stephen: I never trust the users :) but I have no choice - need to trust them to write this function. Also, this is a management requirement... – Yulia V Jun 01 '12 at 12:38
  • If it is a management requirement then it's likely not a functional requirement. – Pablo Ariel May 23 '18 at 20:50

1 Answers1

3

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.

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • @gauden : Thanks -- Although I would have suggested passing a handle to the function around if you hadn't already. I guess seeing my first reaction posted made me think a little deeper about the problem. And ultimately, I think returning the function and passing it around as you proposed is probably the best idea ... The difficult question was "How do I get a handle to the function?"...Then it became, how can I do this a little more safely. I also think this could be cleaned up a bit ... but at some point it's not worth it anymore ;). – mgilson Jun 01 '12 at 14:19
  • :) What can I say, @mgilson? I will delete my answer anyway and leave this idea that you summarise in your own comment -- pass a handle to a function -- for you to add in an edit in yours, if you deem fit. – daedalus Jun 01 '12 at 15:35
  • Thanks for your responses! Just checked python manual (not sure why I have not done it before...) to learn more about exec() and found an example at the bottom of http://docs.python.org/library/parser.html#example-emulation-of-compile that is very similar to mgilson's idea but allows to avoid locals().keys(). Have not tried it yet, but looks promissing. Thanks again. – Yulia V Jun 01 '12 at 15:45
  • @YuccaV : I'm not completely sure that you can get away without the call to `locals` or `vars` or some other form of introspection. At some level, you need to be able to get a handle on the function so you can use it. (compile won't give you the handle -- just an object suitable to `exec` later). If you do manage to do it without the call to locals -- I'm interested to see it. Make sure you post it and let me know. – mgilson Jun 01 '12 at 15:58
  • @YuccaV : Also note that `set(locals().keys())` is the same as `set(locals())` (I'll edit to clean that up a bit). – mgilson Jun 01 '12 at 15:59
  • >>> import parser >>> st = parser.expr('a + 5') >>> code = st.compile('file.py') >>> a = 5 >>> eval(code) 10 Seems that eval() returns the value of the function that results from parsing – Yulia V Jun 01 '12 at 17:04
  • @mgilson Do you think this will work or there is any hidden downside? I have used python for 3 weeks or so, cannot be sure. – Yulia V Jun 01 '12 at 17:06