I can’t see any issue here with your understanding of while
, if
or try
.
The issue is your understanding of functions.
The most important thing to understand is that a function is not simply a label for a place where you can “jump” in the code. A function is a self-contained mini-program: it takes in some input from its parameters, and gives back a result via return
.
So, we don’t want to call confirm_tax_year
again from _year_in_range
to “restart” the loop. Instead, we want confirm_tax_year
to use the result to decide whether to break
.
We use if
to make a decision, as you understand well enough; and here we can simply nest that underneath the else:
of your try
block. That is: in the cases where there is no ValueError
, we should next check if
the _year_in_range
.
In order to make that decision sensibly, _year_in_range
needs to tell us whether the year is in range. So it should return a boolean value: True
(the year is in range) or False
(it isn’t).
With those changes, we get:
def confirm_tax_year():
while True:
try:
year = int(input("Which year are you calculating tax for? (Taxbuddy can calculate from 2020 to 2024) "))
except ValueError:
print("Invalid input try again")
else:
if _year_in_range(year):
break
def _year_in_range(year):
if year in range(2020,2025):
return True
else:
print("This year is not in the range 2020 to 2024 please try again")
return False
But that said, with more understanding, we can structure the code more clearly.
At this point, you may think that separating out the function isn’t helping very much - it only does its own if
check that we could use directly instead, and sometimes print
s a message. Here’s what it looks like without separating out the function:
def confirm_tax_year():
while True:
try:
year = int(input("Which year are you calculating tax for? (Taxbuddy can calculate from 2020 to 2024) "))
except ValueError:
print("Invalid input try again")
else:
if year in range(2020,2025):
break
else:
print("This year is not in the range 2020 to 2024 please try again")
Notice there isn’t anything to return
any more, because a) we won’t use that information to make a decision (we already made it) and b) now it would “return” from confirm_tax_year
, which we don’t want to do.
There’s another trick we can do to simplify this. Instead of having an else
for the try
- if we make sure that the code is only reached when there is no exception, we can put the next part of the code right after, and save the else
. To do that, we need to think about what happens when there is an exception. In this case, we don’t want to run the year-checking code, but we do want to ask for another input
. The natural way to get that result is to continue
the loop. So:
def confirm_tax_year():
while True:
try:
year = int(input("Which year are you calculating tax for? (Taxbuddy can calculate from 2020 to 2024) "))
except ValueError:
print("Invalid input try again")
continue
if year in range(2020,2025):
break
else:
print("This year is not in the range 2020 to 2024 please try again")
When it comes to this kind of “validation loop”, my preference is to turn it into a series of checks for each thing that can go wrong. Each one is tried, and when something is wrong, the loop continue
s. Then after everything is checked, we can break
unconditionally:
def confirm_tax_year():
while True:
try:
year = int(input("Which year are you calculating tax for? (Taxbuddy can calculate from 2020 to 2024) "))
except ValueError:
print("Invalid input try again")
continue
if year not in range(2020,2025):
print("This year is not in the range 2020 to 2024 please try again")
continue
break
Let’s also separate the request for input
(since that should always succeed, and just give us a string) from the checks:
def confirm_tax_year():
while True:
year = input("Which year are you calculating tax for? (Taxbuddy can calculate from 2020 to 2024) ")
# make sure it's an integer
try:
year = int(year)
except ValueError:
print("Invalid input try again")
continue
# make sure it's in range
if year not in range(2020,2025):
print("This year is not in the range 2020 to 2024 please try again")
continue
break
At this point, we can experiment with functions again. It should be easy enough to start using the fixed _year_in_range
again, if you wanted. Maybe you might also imagine a _year_is_integer
function. There’s a problem here: you would need to know whether the int
call succeeded, but you also need to get the actual integer result. So you might for example return a tuple of those two values (you’ll have to make something up for the “integer result” when the call failed - just to have a consistently structured result; it doesn’t really matter, since the main loop won’t use that value).