How to / is that possible to turn off short-circuit evaluation?

Viewed 94

I am coding a kind of command line user interface where an arbitrary boolean expression can be given as input. This expression must be repeatedly evaluated on a dictionary that is changing after some update.

Here is the simplified code :

import traceback


def update_my_dict():
    return {'a': 1}


my_dict = {'a': 0}

bool_exp = input()

# -- verify code can be executed in the current context
try:
    result = eval(bool_exp, my_dict)
except Exception:
    print(f'Expression cannot be evaluated, evaluation raise the following error:')
    print(traceback.format_exc())
    quit()

# -- verify code return a boolean
if not isinstance(result, bool):
    print(f'Expression return {type(result)}, expect bool')
    quit()

# -- go
while not eval(bool_exp, my_dict):
    my_dict = update_my_dict()

Before running the last while loop I want to verify that the expression can be executed in the current context and ensuring that its return a boolean.

My problem is if the expression is, for example bool_exp = a == 1 and b == 2 the first test evaluation of the expression while return false but do not raise exception because off lazy evaluation. But when my_dict is updated then an error will be raised.

So is that possible to, some how, disable the lazy/short-circuit evaluation for the first test evaluation ? I searched some solution using ast, but it seems complicated since bool_exp can be arbitrary long and complex, like containing entangled boolean expressions and so on.

PS: I know eval() is unsafe in a general context but my code will not be usable externaly

PS2: I know its possible to catch the exception in the while loop but it looks a bit sub-optimized knowing my_dict keys will never change when updated, only their values. Also this question is more like if its possible to control the evaluation behavior

EDIT

"but you could use & and | instead of and and or !" No. I cannot tell what will be entered as input. So the user can input whatever s.he want.

A Correct input expression:

  1. Should return a boolean.
  2. Should only involve dictionary keys in the tests.

"Correct" meaning it will be repeatedly evaluated in the ending while loop without raised any exception. We assume that the keys in the dictionary will stay constant. i.e only the values of the dict will change during the update phase. In other words, the first try/except aim to verify that the expression is only doing some test on variables that are in my_dict.

I cannot explain further the global use of this or I'll need a very long post with lost of what seems irrelevant information to resolve the issue.

1 Answers

You could put the "verification" code inside the loop. This makes sense as your input is changing so you should verify it on each change. You already evaluate the expression every time the dict values change, so the only added logic compared to your current code is that the isinstance check is now done as well with every change of the dict:

import traceback

def update_my_dict():
    return {'a': 1}

my_dict = {'a': 0}

bool_exp = input()

# -- go
while True:
    # -- verify code can be executed in the current context
    try:
        result = eval(bool_exp, my_dict)
    except Exception:
        print(f'Expression cannot be evaluated, evaluation raise the following error:')
        print(traceback.format_exc())
        quit()

    # -- verify code return a boolean
    if not isinstance(result, bool):
        print(f'Expression return {type(result)}, expect bool')
        quit()

    if result:
        break

    my_dict = update_my_dict()

On the same example input a == 1 and b == 2 this will output:

Expression cannot be evaluated, evaluation raise the following error:
Traceback (most recent call last):
  File "main.py", line 14, in <module>
    result = eval(bool_exp, my_dict)
  File "<string>", line 1, in <module>
NameError: name 'b' is not defined
Related