pythonevalinfinite-loop

Why does my code go into an infinite loop?


I have the following code at the moment:

executed = False

while executed == False:
    try:
        inp = input("Enter the variable you want to change (e.g.: limit = 10): ")
        eval(inp, globals())
    except:
        print("Invalid input")
        continue
    print("Variable has been changed")
    executed = True

and I don't quite understand why if I put exit() as an input it goes into an infinite loop outputting the following line over and over again: Enter the variable you want to change (e.g.: limit = 10): Invalid input without me further inputting anything.

I am absolutely lost on why it even goes into an infinite loop since I expected it to either completely quit out of it or just print "Invalid input" and then ask me to input something different again. Why does it keep skipping the input() part and directly jumps to the eval part? (Or at least I assume that's what it's doing).


Solution

  • To debug the problem, change the code to following:

    from time import sleep
    
    executed = False
    
    while executed is False:
        try:
            inp = input("Enter the variable you want to change (e.g.: limit = 10): \n")
            eval(inp, globals())
        except BaseException as e:
            print("error is:", e, type(e))
            print("Invalid input")
            sleep(2)
            continue
        print("Variable has been changed")
        executed = True
    

    Now the following is happening:

    1. when you enter exit(), eval can correctly execute the command exit().
    2. It throws a SystemExit exception.
    3. BUT, you'll catch it inside your bare except clause(which is equivalent to except BaseException), which means you don't let the script to be finished by that SystemExit exception.
    4. But it seems like it closes the "standard input" file desciprtor.
    5. In the next iteration, when it hits, input(), it cannot read from a closed file(stdin).
    6. From now on, you'll get a ValueError forever.

    Update

    Yes I checked the source code and it indeed closes the stdin.

    Here it adds exit callable to the builtins:

        builtins.exit = _sitebuiltins.Quitter('exit', eof)
    

    and this is what gets called when you say `exit():

    class Quitter(object):
        ...
        def __call__(self, code=None):
            # Shells like IDLE catch the SystemExit, but listen when their
            # stdin wrapper is closed.
            try:
                sys.stdin.close()
            except:
                pass
            raise SystemExit(code)