The ask-again loop
Crashing on the first bad keystroke is not acceptable in real software, and silently accepting garbage is worse. The standard fix combines three tools you already own: while True: from lesson 5-2, try/except from lesson 9-2, and break. Attempt the conversion, and only break out of the loop once it succeeds:
while True: raw = input() try: n = int(raw) break except ValueError: print("not a number, try again")
The break sits inside the try, on the line after the conversion, because that placement encodes the logic: reaching the break proves int(raw) did not raise, so the loop only ends on success. Every command-line tool that asks for a number, a date, or a menu choice runs a loop of exactly this shape.
A stubborn user, handled
The stdin here plays someone getting it wrong twice: abc, then 4.5, which int also rejects since it accepts only whole-number text, and finally 12.
while True: raw = input() try: n = int(raw) break except ValueError: print("not a number, try again") print("got:", n)
Input
abc 4.5 12
Output
not a number, try again not a number, try again got: 12
Each bad line produced one complaint and one more trip around the loop, since the break was skipped both times. The third line converted cleanly, so execution reached the break and the loop ended.
The final print sits outside the loop and can safely use n, because the only route out of the loop was a successful conversion. That guarantee is what makes this shape worth learning as a unit: code after the loop never has to re-check the value.
The break sits on the line after n = int(raw), inside the try, because a raised error jumps to the except before the break can be reached.
That placement turns control flow into the success test. When int(raw) raises a ValueError, Python abandons the remainder of the try immediately, so the break is skipped and the loop comes around for another attempt. When the conversion succeeds, execution simply continues to the next line and the loop ends.
Moving the break would break the logic in visible ways. Putting it after the whole try/except would end the loop on the first attempt regardless of the outcome, and putting it inside the except would end the loop only when the input was bad, which is precisely backwards.
Real validation usually rejects input for more than one reason, and the two kinds sit in different places.
while True: raw = input() try: age = int(raw) if age >= 0: break print("age cannot be negative") except ValueError: print("digits only please") print(f"age recorded: {age}")
Input
twenty -3 20
Output
digits only please
age cannot be negative
age recorded: 20The two failures are genuinely different. twenty cannot become a number at all, so the conversion raises and the handler reports it. -3 converts perfectly well and is rejected on grounds of meaning instead, which is a job for an if rather than an exception.
The break now depends on that if, so it fires only when the value is both convertible and non-negative. Because the negative message sits after the if rather than in an else, reaching it already proves the check failed, and the loop simply continues to the next pass.
With a user typing ten and then 10, the except block executes once.
The first pass attempts int("ten"), which raises a ValueError, so the handler runs and prints its message. The second pass converts "10" without incident and reaches the break, never entering the except at all.
The general rule is that the handler runs once per bad line rather than once per program, since each input line is one pass through the loop. A user who mistyped four times would see four messages, and one who got it right immediately would see none.