nostradamus
nostradamus

Reputation: 722

Alternative to exec

I'm currently trying to code a Python (3.4.4) GUI with tkinter which should allow to fit an arbitrary function to some datapoints. To start easy, I'd like to create some input-function and evaluate it. Later, I would like to plot and fit it using curve_fit from scipy.

In order to do so, I would like to create a dynamic (fitting) function from a user-input-string. I found and read about exec, but people say that (1) it is not safe to use and (2) there is always a better alternative (e.g. here and in many other places). So, I was wondering what would be the alternative in this case?

Here is some example code with two nested functions which works but it's not dynamic:

def buttonfit_press():
    def f(x):
         return x+1
    return f 

print(buttonfit_press()(4))

And here is some code that gives rise to NameError: name 'f' is not defined before I can even start to use xval:

def buttonfit_press2(xval):
    actfitfunc = "f(x)=x+1"
    execstr = "def {}:\n    return {}\n".format(actfitfunc.split("=")[0], actfitfunc.split("=")[1])
    exec(execstr)
    return f

print(buttonfit_press2(4))

An alternative approach with types.FunctionType discussed here (10303248) wasn't successful either...

So, my question is: Is there a good alternative I could use for this scenario? Or if not, how can I make the code with exec run?

I hope it's understandable and not too vague. Thanks in advance for your ideas and input.


@Gábor Erdős:

Either I don't understand or I disagree. If I code the same segment in the mainloop, it recognizes f and I can execute the code segment from execstr:

actfitfunc = "f(x)=x+1"
execstr = "def {}:\n    return {}\n".format(actfitfunc.split("=")[0], actfitfunc.split("=")[1])
exec(execstr)
print(f(4))
>>> 5

@Łukasz Rogalski:

Printing execstr seems fine to me:

def f(x):
    return x+1

Indentation error is unlikely due to my editor, but I double-checked - it's fine. Introducing my_locals, calling it in exec and printing in afterwards shows:

{'f': <function f at 0x000000000348D8C8>}

However, I still get NameError: name 'f' is not defined.


@user3691475:

Your example is very similar to my first example. But this is not "dynamic" in my understanding, i.e. one can not change the output of the function while the code is running.


@Dunes:

I think this is going in the right direction, thanks. However, I don't understand yet how I can evaluate and use this function in the next step? What I mean is: in order to be able to fit it, I have to extract fitting variables (i.e. a in f(x)=a*x+b) or evaluate the function at various x-values (i.e. print(f(3.14))).

Upvotes: 3

Views: 9082

Answers (2)

Dunes
Dunes

Reputation: 40713

The problem with exec/eval, is that they can execute arbitrary code. So to use exec or eval you need to either carefully parse the code fragment to ensure it doesn't contain malicious code (an incredibly hard task), or be sure that the source of the code can be trusted. If you're making a small program for personal use then that's fine. A big program that's responsible for sensitive data or money, definitely not. It would seem your use case counts as having a trusted source.

If all you want is to create an arbitrary function at runtime, then just use a combination of the lambda expression and eval. eg.

func_str = "lambda x: x + 1" # equates to f(x)=x+1
func = eval(func_str)
assert func(4) == 5 

The reason why your attempt isn't working is that locals(), in the context of a function, creates a copy of the local namespace. Mutations to the resulting dictionary do not effect the current local namespace. You would need to do something like:

def g():
    src = """
def f(x):
    return x + 1
    """
    exec_namespace = {} # exec will place the function f in this dictionary
    exec(src, exec_namespace)
    return exec_namespace['f'] # retrieve f

Upvotes: 1

Aruj
Aruj

Reputation: 82

I'm not sure what exactly are you trying to do, i.e. what functions are allowed, what operations are permitted, etc.

Here is an example of a function generator with one dynamic parameter:

>>> def generator(n):
        def f(x):
            return x+n
        return f
>>> plus_one=generator(1)
>>> print(plus_one(4))
5

Upvotes: 0

Related Questions