Try except recursion or while loop? - python

I am doing a python course where they suggested a try and except block in a while loop in order to keep asking for input until the condition is satisfied. Intuitively I feel it is shorter to just call the function again in the "except" block like this:
def exceptiontest():
try:
print(int(input("number 1: "))+int(input("number 2:")))
except:
print("a mistake happened")
exceptiontest()
exceptiontest()
When asking on the forum on the course I got the reply that it is not the same. I am a bit confused now. Anyone that can clarify for me? Thanks in advance!

Calling the function in the except will eventually raise a RecursionError: maximum recursion depth exceeded error if you keep entering bad inputs. Generally must humans won't be entering that many bad data to hit the error before they give up, but you are unnecessarily putting function calls on a stack.
A while loop is better since it's one function call, waiting for a valid input. IT doesn't waste any more resources than it needs.

while loop, for two reasons
it's clearer to read: while not success, try again
recursion is not free. It leaves the previous function stack open. it could run out of memory (probably won't, in this case, but in principle, avoid it)

Another reason to use the while loop which has not yet been mentioned is that you could leverage the assignment expressions coming with Python 3.8.
The function add encapsulates getting two numbers and trying to add them.
def add():
'try to add two numbers from user input, return None on failure'
x = input('number 1: ')
y = input('number 2: ')
try:
return float(x) + float(y)
except TypeError, ValueError:
return None
The following while loop runs as long as there is no result.
while (result := add()) is None:
print('you made a mistake, make sure to input two numbers!')
# use result

Related

python try statement or if statement

Hello everyone I have my code almost done but I'm trying to add in some sort of check to avoid errors. But I'm not really understanding what statement would be better to use to test the code. I know there are a few options of either using a loop, if-statement, or try. But here is the code in regards to doing captcha. I need it to run the first set of code which if the captcha doesn't pop up I continue on. But if the captcha does pop up solve it then continue on.
Some times captcha doesnt appear and if I run the whole set of code I get an error because we are expecting captcha to pop up.
Or if the captcha does appear to solve it which would.
I would really appreciate any help please as I'm not sure the right statement to use.
try should be used when something would return an error and would otherwise cause your program to stop/crash. An example of this would be:
try:
import pandas
except:
print("Unable to import pandas. Module not installed")
In this example your program will attempt to import the pandas module. If this fails it will then print out a line of text and continue running.
if statements are used to decided when to do something or not based on the returned logic. The key difference is that logic IS returned and not an error.
if x > 10:
print("This is a large number")
else:
print("This is a small number")
With this example, if 'x' did not exist it would produce an error, no more code will be executed, and the program will crash. The main difference between IF and TRY is whether logic is returned as true/false or is something just plains fails.
With your specific example it is important to know if the captcha appearing or not will break your code. Does the logic boil down to captcha = false or does captcha not exist at all and logic fails entirely?
Q: How do you define sometimes captcha doesn't appear (1%, 20%, 50%, ...)?
A: Maybe 5% of the time captcha doesn't appear.
In this case, I prefer to use Exception handling: do stuff and if something goes wrong, fix it
try:
# > 95% of the time the code below works when the captcha appears
except SomeException:
# < 5% of the time the code is called when the captcha doesn't appear
IMHO, you have not really 2 different codes: you have one and a fallback solution, it's really different than:
if x % 2:
...
else:
...

Why do we need an except block for the try/except in Python?

I get what it does but I don't understand why we do it? Why are we trying to except something?
For example:
for i, value in enumerate(arr):
try:
result_dict[value] += 1
except KeyError:
result_dict[value] = 1
Why do I have to do except KeyError? I don't know why a KeyError is thrown. Why can't I just do
result_dict[value] += 1
in the first place?
What goes inside the except block for any try/catch? I get the error has to be thrown but what condition has to do inside the except block?
Sorry if my question is too dumb. I'm a beginner.
TLDR
try/except blocks makes sure that your program continues running rather than ending abruptly. By including a except KeyError, we are specifically making sure that the program does not end abruptly if a KeyError occurs. result_dict[value] += 1 will throw an error if value is not a key in the dictionary, because it tries to do access a key that does not exist. The += makes the code run similarly to:
result_dict[value] = result_dict[value] + 1
and since value is not in result_dict, it is similar to saying
result_dict[value] = None + 1
which is bad.
The Non-TLDR version
When an error occurs in python, the program usually terminates abruptly and exits. It does not run any code that occurs below the part where the exception occurs. For example, take the following code. It takes 2 numbers from the user a and b, and it will output a/b
a = int(input("Enter a value for a: "))
b = int(input("Enter a value for b: "))
print("a/b is:", a/b)
If the user gives a valid input (say a=4, b=2) the program proceeds smoothly. However if the user were to give, say a = "c", then the following happens
Traceback (most recent call last):
File "<string>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'c'
And the program ends abruptly. It is perfectly valid to have the program end like that, but rarely do you want the program to end abruptly. Take the case where the user has 1000 inputs, and the last one messes up. The user will then have to restart the program and re-input all 1000 inputs again, because the program ended abruptly.
So now we introduce a try/except block. We know that converting a non-numeric character into an integer will throw a ValueError as seen in the error, so we will handle them accordingly
while True:
try:
a = int(input("Enter a value for a: "))
break
except:
print("An error has occurred. Please input a again")
while True:
try:
b = int(input("Enter a value for b: "))
break
except:
print("An error has occurred. Please input b again")
print("a/b is:", a/b)
So now, we take an input from the user, try to convert it into an integer and put that value into a. If it succeeds, it will proceed smoothly to the next line (break) and exit the loop. If it fails, then an exception will occur, and it will enter the except block, where it will print a helpful error message, and run the loop again (until the user enters a valid input). Now the program doesn't just terminate abruptly when it fails. It gives appropriate feedback to the user, and lets the user retry the input again.
That is a general exception block, where you just catch any exception.
But let's now say that there are multiple possible errors that could occur. Take the following code:
a = input()
b = input()
print(int(a)/int(b))
print("I am done")
Now a few errors can occur. One of them is the ValueError stated above, where the input given cannot be converted into an integer. The other error, is a ZeroDivisionError where b=0. Python doesn't like dividing by zero, hence it will throw an exception and terminate the program immediately.
So now, you want to print a special message for each type of program. How you do that is by catching specific exceptions
a = input("Enter a value for a: ")
b = input("Enter a value for b: ")
try:
print(int(a)/int(b))
except ValueError:
print("We cannot convert a non-numeric character to an integer")
except ZeroDivisionError:
print("We cannot divide by 0")
except:
print("Some unexpected error has occurred")
print("I am done")
If python was unable to convert the input into an integer, it will enter the ValueError block of the code, and print "We cannot convert a non-numeric character to an integer"
If python tried to divide by 0, then it will enter the ZeroDivisionError block and print "We cannot divide by 0"
If any other type of error occurred, it will end up in the final except block and print "Some unexpected error has occurred"
And after the exception is handled, it prints "I am done"
Note that if the final except block was not there to catch any other exceptions, then the program will not terminate nicely because the exception was unhandled, and it will not print the final statement.
Now, how does one realise what possible error can occur? That is up to practice, and once you get familiar with the errors, you can handle them accordingly. Or, purposefully make your program throw exceptions, and read the stack trace for what kind of exceptions occur. And then handle them accordingly. You don't have to handle each specific error differently, you could handle all of them in the same way.
You can read more here: Python's documentation for errors and exceptions
Here's why. You cant add to something that is None. For example say var1 = None. I couldn't do something like var = var + 1. That would throw an error. In the example you shared the code will check if you can add 1 to value's key. If you cannot do this you assign a value to the key.
for i, value in enumerate(arr):
try:
#Try and add 1 to a dict key's value.
result_dict[value] += 1
except KeyError:
#Say we get an error we need to assign the dict key's value to 1.
result_dict[value] = 1
A KeyError will crash/terminate your program if result_dict[value] doesn't exist. By using an except block, you tell the program what to do in the event of a KeyError (here, assign result_dict[value] = 1 if it doesn't exist), and then the program continues. Basically, you're avoiding a crash.
Let's say your value is "banana". If you don't have a result_dict["banana"], you can't add 1 to nothing, i.e. None += 1.
By using except KeyError, you intercept the error before it stops your program, and now you have set a key-value pair for your result_dict, rather than terminating the program.
Let's start with an example
We have tenants in an apartment building who can reserve parking spots. Some tenants have no car and won't have a parking spot, but only tenants can reserve a spot. We track parking spots in a dictionary:
parking_spots = { "alice": 1, "bob": 2, "chris": 0 }
Chris doesn't have spots because he walks to work.
What should happen if Eve tries to reserve a spot?
parking_spots["eve"]
That code asks "how many spots has eve reserved?" However, another question this answers is whether Eve is a tenant of the building at all. Python represents this by having parking_spots["eve"] throw a KeyError which is not like any other value. If python didn't do this and returned 0 by default, then parking_spots["eve"] and parking_spots["chris"] would look the same.
But wait, I know that this particular dictionary isn't being used that way
Cool, that's pretty common. It's so common in fact that there are multiple ways of doing this.
Instead of
result_dict = {}
for i, value in enumerate(arr):
try:
result_dict[value] += 1
except KeyError:
result_dict[value] = 1
You can use
result_dict = {}
result_dict.setdefault(0)
result_dict += 1
Or you can use defaultdict.
from collections import defaultdict
result_dict = defaultdict(int)
result_dict += 1
Some CS Theory
There are two theoretical concepts that we could talk about here:
Exceptions as a control flow mechanism
The api of a dictionary
Exceptions for Control Flow
try/catch is style of control flow. It is simply a nicer way to think of code for certain cases. It is almost never required as long as you can use if/else, but is many times the best way to think about code.
Depending on who you talk to, exception handling can be a somewhat controversial feature:
C++ - Arguments for Exceptions over Return Codes
In my opinion, it's rare that exceptions are truly the right answer if you design a system from scratch. Generally, I prefer using an Option (or more generally sum types).
However, in many cases, they are the best answer given you starting constraints.
Dictionary API
Python throwing an exception when the key isn't present is a design choice and not the only way the API could be written. Instead, it could simply return None. Many languages follow the latter approach as they consider it more safe - especially when you work in a modern typed language like Rust, Haskell, Elm, etc.
Further Reading
I would also encourage you to read Amp's answer as it covers some other details on the particulars of exception handling that may be instructive.

Recursive call causing IndexError in Class function

The goal of my program is to have the computer ask a user questions, and return some specifications for a computer that would fit their needs. Right now I am working on QuestionAsker, which is responsible for, as the class name suggests, asking the user questions. I've been hung up on the 4th line of AskQuestion() function. Before I tell you the problem, take a look at the code:
from question import Question
class QuestionAsker():
questions = [
Question("At minimum, what should your game be running on?", ["Low", "Medium", "Ultra"]),
Question("On a scale of 1-3, how much flair do you want on your computer?", ["Low", "Medium", "Ultra"]),
Question("Money doesn't grow on trees. How much money is in your budget?", ["$500", "$1000", "$2000+"]),
]
index = 0
def AskQuestion(self):
userInputForQuestion = raw_input(self.questions[self.index].question + " ")
if userInputForQuestion not in self.questions[self.index].answers:
print("Try again.")
self.AskQuestion()
self.questions[self.index].selectedAnswer = userInputForQuestion
self.index += 1;
def resetIndex(self):
self.index = 0
def ReadQuestions(self):
pass
I was testing this code by calling AskQuestion a couple of times (to loop through all the questions), and to make sure this code was tippy top, I supplied multiple answers that returned "Try again," as it should. The issue is that if I supplied more than 1 wrong answer to a question, but then if I answered correctly after multiple wrong answers, I would get the following error message:
IndexError: list index out of range
I immediately suspected the [self.index] of the self.questions[self.index], so I began printing out the index to the console. I thought that the issue was that the AskQuestion was magically incrementing the self.index, on the final line of the AskQuestion function, but no. It kept printing a consistent number, in the case of the first question, 0!
I'm at my wits end here, and the other questions I've seen on this haven't served much help. Hopefully you guys can help, thanks!
Please take care that in your function body, when a wrong answer is given, the function does not end. It makes a recursive call. When that call ends, the index is still incremented. So the wrong answers still mess the index up.
You should end the function after the wrong call is made, for what you want to happen.
if userInputForQuestion not in self.questions[self.index].answers:
print("Try again.")
self.AskQuestion()
return None
or use else.
if userInputForQuestion not in self.questions[self.index].answers:
print("Try again.")
self.AskQuestion()
else:
self.questions[self.index].selectedAnswer = userInputForQuestion
self.index += 1;
Also please note that using recursion in such a way is not very common. Which made you make the mistake anyway.

except ValueError not tripping Python 3.4

Alright so I'm trying to basically prevent someone from typing a string value into the field:
#User selection
print("Which program would you like to run?")
print("(Type '9' if you wish to exit the menu)")
selection = int(input())
print()
#Security statement followed by case statement
while selection <= 0 or selection >= 10:
try:
print("That is an invalid selection, please input a proper selection.")
print("Which program would you like to run?")
selection = int(input())
print()
except ValueError:
print("Cmon man")
Plain and simple, it's not running. I've tried reorganizing everything and I haven't found a proper solution. Been looking around for almost an hour now. No help to the issue. Any kind souls?
Ignore the case statement portion btw, that's not even written yet.
P.S. Just keep getting usual "String isn't a number durr" response
("ValueError: invalid literal for int() with base 10: 'why'")
P.P.S. Issue is already pointed out. I'm apparently stupidly oblivious lol... Thanks for the help.
Your try...except doesn't cover the initial user input and so the ValueError isn't actually caught.
If you enter an int outside the bounds defined (0 >= x >= 10) as the first input then you can see the try...except blocks working.
You'll need to refactor your code so that the first input request is inside your try block and the loop or wrap the existing input request in another try...except.
Also, as a side note, input() can take a string argument that will be displayed as a prompt to the user.
selection = int(input("Which program would you like to run? "))

Guidance on exceptions given by the Python docs

According to the Python documentation on idioms and anti-idioms in relation to exceptions: "You should try to use as few except clauses in your code as you can — the ones you do use will usually be inside calls which should always succeed, or a catch-all in a main function." Taking this sentence in sections...
"You should try to use as few except clauses in your code as you can"
A bit confusing for a newbie like myself, I thought it was good practise in Python to use the EAFP style -i.e. many try and except statements. Or am I missing the point?
"the ones you do use will usually be inside calls which should always succeed"
I don't understand the point that's being made here.
"or a catch-all in a main function."
So it's good style to any let code that throws an exception to simply pass it up the call stack until it reaches the top level where you have really generic exception handling?
"You should try to use as few except clauses in your code as you can"
It's easy to litter your code with exceptions:
def solve_linear(mat1, mat2):
det1 = determinant(mat1)
det2 = determinant(mat2)
try:
return det1 / det2
except ZeroDivisionError:
raise NoSolution
Here, it's probably fine to let the ZeroDivisionError propagate. You don't need to catch it.
"the ones you do use will usually be inside calls which should always succeed"
For example, look at this code which reads a file, or returns a cached value. It normally succeeds, in spite of the KeyError exception:
def read_file(path):
try:
return cache[path]
except KeyError:
fp = open(path, 'rb')
data = fp.read()
fp.close()
cache[path] = data
return data
"or a catch-all in a main function."
If your program is interactive, you'll probably want to catch almost everything at the top level. Here's the top loop of an interactive command-line program:
def main():
while True:
try:
text = raw_input('enter command: ')
cmd = parse_command(text)
except EOFError:
return
except KeyboardInterrupt:
return
except ValueError:
print 'Syntax error'
continue
try:
run_cmd(cmd)
except SystemExit:
raise
except Exception:
traceback.print_exc()
Concerning the first point, the whole point of using exceptions is that you don't have to wrap every line in one! E.g. in C, errors are usually determined by the return value of a function call. So you have to check those after every call if you want to catch all errors. With Python you can group a (possibly large) block of statements that go together in a try/except block and only deal with all errors once.
The second point is that (if possible) you want to solve failures close to the point where they occur. E.g. you are reading data from a network and get zero bytes. In that case it is usually perfectly allright to wait and try again.
The last point is that sometimes an error is so big that it cannot be handled at a low level. E.g. if you are trying to open a file that not exist, it wil fail. And your program cannot do whatever it was going to do with the contents of the file. It is best to handle that at the top level of the program and ask the user for another file name or maybe quit the program.
I think the point is that exceptions should be used only for 'exceptional' circumstances. That is the meaning behind the use "in calls which will usually succeed". An example of this might be some computation that under some really weird circumstances ends having to do a division by zero. Then you can enclose that in a try / except statement to deal with that possibility.
The catch-all in a main function would be applicable to the same scenario. Say your calculations end up with a division by zero somewhere deep in the call stack. From this point you can't continue, so there's no point in having a try/except statement right at the point of failure. It would make more sense to just have one at a higher level where you can reasonably recover from the error.
The example they give in the documentation is an example of this. When calling 'get_status' you would expect the file to be there. If it doesn't then you have the except statement to deal with that (although, as mentioned, in that particular case the 'with' statement is much better).
The Python philosophy is typically to "ask forgiveness, not permission." But the idea isn't to use a try-except clause to catch ALL possible errors. Ideally, each try-except will only catch the error(s) that are relevant.
BAD (doesn't specify a specific exception type):
a = [1, 2, 3]
i = 3
try:
print a[i]
except:
print "Not found!"
GOOD (only handles the exception we expect to get):
a = [1, 2, 3]
i = 3
try:
print a[i]
except IndexError:
print "Not found!"
The reason this is important is so we don't obscure other possible code errors. What if, in this example, i was 1.8? The first example would print Not Found!, obscuring the real issue, but the second example would return TypeError: list indices must be integers, not float letting us know that there's a logical flaw in our code.

Categories