Search code examples
pythonfunctionpython-3.xevalcalculator

Using user defined variables/functions in eval [Python 3.4.2]?


I have a calculator (python 3.4.2) that can do normal operations using eval.

def calculator(user_input):
    if any(c not in config.valid_cal_chars for c in user_input):
        print("- Invalid Equation | Bad characters")
        return
    elif not any(c in user_input for c in "0123456789"):
        print("- Invalid Equation | No numbers found")
        return
    sys.stdout.write("calculating " + "-".join(gfx.load_sequence))
    time.sleep(0.1)
    print (" | 100%")
    try:
        current_ans = eval(user_input)
    except (SyntaxError, ZeroDivisionError, NameError, TypeError, ValueError):
        print ("- Invalid Equation | Error")
        return
    config.ans = current_ans
    print (current_ans)

Here is the config.py that config.ans, config.valid_cal_char is refering to:

ans = ("0.0") 
valid_cal_chars = ("0123456789-+/*ansqrt() \n")

And if you are wondering what the

user_choice

variable is referring to, it is referring to a input function that I have before this function. That part works, so no need to worry there.

However, I am wondering if it is possible to do something like this:

input equation here: 4*4 #this would be saved as the user_input variable
> 16 #the output of the equation
input equation here: sqrt(ans) #this would use the previous answer saved in config.ans (ans) and the sqrt() to find the square root of the previous printed value, so:
> 4

So typing ans would result in:

input equation here: 1+1
> 2
input equation here: ans
> 2

And using sqrt() would result in:

input equation here: 2+2
> 4
input equation here: sqrt(4)
> 2

So if you still don't get it, sqrt() find the square root of the value inputted. ans uses the previous returned value. So combining the two "sqrt(ans)" would give the square root of the previous returned value.

With the back ground info out of the way, what I want to do is to allow a user to use these when calculating. While the "eval" may not work, I am happy to use "exec" too (knowing the dangers). However, here is a multitool.py (the main file) that imports the this file (functions.py) to use all the functions that I have in there, including this one.

import os, sys, glob, math, random, login, gfx, config, functions, time

path = "******" #creates path to folder (can be changed by commenting this line out and creating new one)
dirs = os.listdir( path ) #not used currently

functions.load_sequence_complete()

functions.username_login()
time.sleep(0.05)
functions.password_login()
print ("\n[credentials have been verified! proceeding to main program " + "-".join(gfx.load_sequence) + "]\n")
time.sleep(0.1)

program = True
while (program == True):
    user_choice = functions.choice_selecter()
    functions.validate_choice(user_choice)

If there are any other info that you need, please put that in the comments or answers below so I can edit this to help you help me :)


Solution

  • You can do it in the first method , you can define ans as a global in the start of the function, and then the result of eval(user_input) , and use ans instead of current_ans everywhere.

    How your function would look like , assuming everything works -

    def calculator(user_input):
        global ans
        if any(c not in config.valid_cal_chars for c in user_input):
            print("- Invalid Equation | Bad characters")
            return
        sys.stdout.write("calculating " + "-".join(gfx.load_sequence))
        time.sleep(0.1)
        print (" | 100%")
        try:
            ans = eval(user_input, {'ans':ans,'sqrt':sqrt},{})
        except (SyntaxError, ZeroDivisionError, NameError, TypeError, ValueError):
            print ("- Invalid Equation | Error")
            return
        config.ans = ans
        print (ans)
    

    Please note I got rid of the elif part in the function, as otherwise, it would not let an input like - ans - go through - you may rethink how to rewrite that.

    Example/Demo -

    >>> def calculator(user_input):
    ...     global ans
    ...     if any(c not in "0123456789-+/*ansqrt() \n" for c in user_input):
    ...         print("- Invalid Equation | Bad characters")
    ...         return
    ...     time.sleep(0.1)
    ...     print (" | 100%")
    ...     try:
    ...         ans = eval(user_input, {'ans':ans,'sqrt':sqrt},{})
    ...     except (SyntaxError, ZeroDivisionError, NameError, TypeError, ValueError):
    ...         print ("- Invalid Equation | Error")
    ...         return
    ...     print (ans)
    ...
    >>>
    >>> calculator('1+2')
     | 100%
    3
    >>> calculator('ans')
     | 100%
    3
    >>> from math import sqrt
    >>> calculator('sqrt(ans)')
     | 100%
    1.7320508075688772
    

    Though you can also use eval as -

    ans = eval(user_input, {'ans':ans,'sqrt':sqrt},{})
    

    This would restrict eval to only take ans and sqrt as that names.

    But still you should reconsider using eval() , as even after restricting the globals and locals in it, users can still cause harm. Why? Check here.