The following questions
I'm not a Python coder, so I can't supply Python code. But I think I can provide a simple scheme that miminizes your dependencies and still runs pretty fast.
The key here is to build something which is a close to eval without being eval. So what you want to do is "compile" the user equation into something which can be evaluated fast. OP has shown a number of solutions.
Here is another based on evaluating the equation as Reverse Polish.
For the sake of discussion, assume that you can convert the equation into RPN (reverse polish notation). This means operands come before operators, e.g., for the user formula:
sqrt(x**2 + y**2)
you get RPN equivalent reading left to right:
x 2 ** y 2 ** + sqrt
In fact, we can treat "operands", (e.g., variables and constants) as operators that take zero operands. Now everying in RPN is an operator.
If we treat each operator element as a token (assume a unique small integer written as "RPNelement" below for each) and store them in an array "RPN", we can evaluate such a formula using a pushdown stack pretty fast:
stack = {}; // make the stack empty
do i=1,len(RPN),1
case RPN[i]:
"0": push(stack,0);
"1": push(stack,1);
"+": push(stack,pop(stack)+pop(stack));break;
"-": push(stack,pop(stack)-pop(stack));break;
"**": push(stack,power(pop(stack),pop(stack)));break;
"x": push(stack,x);break;
"y": push(stack,y);break;
"K1": push(stack,K1);break;
... // as many K1s as you have typical constants in a formula
endcase
enddo
answer=pop(stack);
You can inline the operations for push and pop to speed it up bit. If the supplied RPN is well formed, this code is perfectly safe.
Now, how to get the RPN? Answer: build a little recursive descent parser, whose actions append RPN operators to the RPN array. See my SO answer for how to build a recursive descent parser easily for typical equations.
You'll have to organize to put the constants encountered in parsing into K1, K2, ... if they are not special, commonly occuring values (as I have shown for "0" and "1"; you can add more if helpful).
This solution should be a few hundred lines at most, and has zero dependencies on other packages.
(Python experts: feel free to edit the code to make it Pythonesque).