2025, Nov 14 13:00

Reliable Python input validation for interactive scripts: retry per item until valid or quit

Learn how to fix Python input validation in interactive scripts: use nested loops to retry the same item, accept only permitted values, or quit cleanly with q.

Reliable input validation in interactive scripts is easy to overlook. A common trap appears when a loop advances to the next item after an invalid entry, silently skipping the current one. Below is a concise walkthrough of how to keep the prompt on the same item until a valid value (or an explicit quit) is provided.

Problem

You need to collect five scores, each tied to a specific dilution. Only a predefined set of strings is allowed, and entering q should terminate the session and display what has been captured so far. The current approach moves on to the next dilution after an invalid entry instead of re-asking for the same one.

Problematic code

The following snippet demonstrates the issue. It accepts input once per dilution, and if the value is invalid, it simply continues with the next dilution rather than retrying.

series = ["1:16", "1:32", "1:64", "1:128", "1:256"]
permitted = ["+", "++-", "-", "+-", "-a", "A"]
ledger = {}
for ratio in series:
    mark = input("Enter score: ")
    try:
        if mark in permitted:
            ledger[ratio] = mark
        elif mark == "q":
            print("Done!")
            break
        else:
            print("Not a valid score!")
    except TypeError:
        print("Invalid input! Try again")
        break
print(ledger)

Why it fails

After an invalid value, the code prints a message and immediately proceeds to the next dilution in the outer loop. There is no mechanism to repeat the prompt for the same dilution. Additionally, the exception handling does not help here because the shown logic does not raise a TypeError: reading input and checking membership in a list won’t trigger that exception. As a result, invalid entries cause silent gaps in the mapping of dilutions to scores.

Solution

The straightforward fix is a nested loop. For each dilution, keep prompting until a valid score is provided or q is entered. This guarantees one of two outcomes for each item: a recorded score, or an immediate stop.

series = ["1:16", "1:32", "1:64", "1:128", "1:256"]
permitted = ["+", "++-", "-", "+-", "-a", "A"]
ledger = {}
for ratio in series:
    while ratio not in ledger:
        mark = input("Enter score: ")
        if mark in permitted:
            ledger[ratio] = mark
        elif mark == "q":
            print("Done!")
            exit()
        else:
            print("Not a valid score! Try again")
print(ledger)

If you prefer not to terminate the entire process with exit(), use a flag to break out of the inner loop and then stop the outer loop.

series = ["1:16", "1:32", "1:64", "1:128", "1:256"]
permitted = ["+", "++-", "-", "+-", "-a", "A"]
ledger = {}
stop_now = False
for ratio in series:
    while ratio not in ledger:
        mark = input("Enter score: ")
        if mark in permitted:
            ledger[ratio] = mark
        elif mark == "q":
            print("Done!")
            stop_now = True
            break
        else:
            print("Not a valid score! Try again")
    if stop_now:
        break
print(ledger)

Why this matters

Input validation should be deterministic: either you capture a value for the current item, or you explicitly stop. Allowing the loop to advance on bad input creates inconsistent or incomplete data and makes outcomes hard to reason about. A small structural change—introducing a sub-loop per item—keeps the control flow aligned with the requirements.

Takeaways

Bind validation to the item you are processing. Use a nested loop to retry until the input is acceptable or a quit token is received. Avoid exception handling when no exception is expected; it adds noise without improving correctness. Choose a clear exit strategy: either terminate immediately or set a flag to unwind gracefully. With these adjustments, you ensure every dilution reliably ends up with a score, or the session stops by design.