Videos
I’m a python learner and am pretty ok with the syntax, and am now finding myself struggling with some of the deeper concepts, in this case error handling. Our environment is python running in a pytest framework using some pre-structured functions along with scripts and functions we write ourselves. I have visibility into the scripts and functions I write, along with some of the pre-structured functions, but the lowest level functions are beyond my view and control. I regularly run into exceptions in these lower levels and am trying to understand what’s going on. All the references I find say something like “raising an exception is when an executions error occurs, the error is raised as an exception”. This makes me nuts as they are explaining the term by using the term. So… Q1: what happens when an “exception is raised”? In terms of program execution and control and variables. Q2: if my script calls a function1 that calls a function2 which contains a command that “raises an exception” can I catch and handle it in function1 or in my top-level script, and if so, how? Q3: if I’m asking the wrong question, what should I be asking and/or researching?
Bit of background: I used to do some programming when I was in middle school and high school, with basic, visual basic, and c++. But then I went to college and ended up stopping. So, I haven't really done any coding in like 15 to 20 years. I ended up looking for a program that apparently doesn't exists, so recently, I decided to retake up coding to make it for myself. So I started using SoloLearn, and I just learned about handling and raising exceptions.
I can see the purpose behind exception handling, but I can't see how "raise" would be useful. Could anyone enlighten me as to why you would raise an exception?
The answer to your question is you want meaningful exceptions.
In most contexts, this involves an exception that adds actionable information in one of two ways:
- Useful typing to the developer who might be catching the exception
- Useful error information to the user (and/or to the developer who can translate to that the user)
In your example, you aren't really adding anything to the normal exception. Catching and then effectively reraising the exception isn't useful. You could log this information, in which case you now added some value to the exception, but otherwise it's pointless.
Imagine this scenario:
try:
my_web_function()
except my_custom_web_exception:
do_something_with_this_exception()
It's entirely reasonable to imagine this scenario. You might make an http request. Most libraries have defined exception types, so you could catch those exceptions, and if it happens, do something.
The "something" is dependent on the specific application. In a http exception, maybe it's just retry the request. Etc.
But the point is catching the exception adds value and information and is actionable.
An error I encountered late last week wasn't due to a python script, but the same principle applies. I had the "privilege" of having to use an old version of subversion on a project. I mistakenly mistyped
prompt: svn co https://some.site.com/some/path
The system's response was
svn: OPTIONS of 'https://some.site.com/some/path': 200 OK (https://some.site.com)
This message was not only unhelpful, it was incorrect. (Giving someone an HTTP/HTTPS "200 OK" status when what happened was far from okay is not OK.) Moreover, there are multiple pathways in subversion 1.9 that result in that erroneous "200 OK" error message. A much more helpful message would have been to tell me that I had mistyped the repository's URL.
Strictly speaking, this was not a bug in subversion. It was just a poorly worded error message. After all, subversion did properly detect and report the problem. From a user perspective, this was a huge bug that was mostly fixed in 2010. Thankfully, a quick google search resulted in multiple hits at stackoverflow.com.
Programmers are often taught that they should simply let an exception pass through if they can't do something about it. Instructors as well as students think "doing something" means correcting the problem. In many cases, there is nothing that can be done to correct a problem. This is an overly narrow view of "doing something." Adding context that enables a user to hone in on the problem and then fix it is "doing something."
Another way of looking at letting low level exceptions bubble up is that doing so is a leaky abstraction.
Raise is re-raising the last exception you caught, not the last exception you raised
(reposted from comments for clarity)
On python2.6
I guess, you are expecting the finally block to be tied with the "try" block where you raise the exception "B". The finally block is attached to the first "try" block.
If you added an except block in the inner try block, then the finally block will raise exception B.
try:
raise Exception("a")
except:
try:
raise Exception("b")
except:
pass
finally:
raise
Output:
Traceback (most recent call last):
File "test.py", line 5, in <module>
raise Exception("b")
Exception: b
Another variation that explains whats happening here
try:
raise Exception("a")
except:
try:
raise Exception("b")
except:
raise
Output:
Traceback (most recent call last):
File "test.py", line 7, in <module>
raise Exception("b")
Exception: b
If you see here, replacing the finally block with except does raise the exception B.
As Russell said,
A bare
raisestatement re-raises the last caught exception.
It doesn't matter whether this is happening in a try-except block or not. If there has been a caught exception, then calling raise will re-raise that exception. Otherwise, Python will complain that the previously caught exception is None and raise a TypeError because None is not something that can actually be raised.
As tdelaney said, it doesn't seem to make sense to do this except in an error-handling function. Personally I'd say that it doesn't even belong in an error-handling function, as the raise should still be in the except clause. Someone could use this in an attempt to execute code whether or not an error occurs, but a finally clause is the proper way to do that. Another possibility would be using this as a way to determine if an error occurred while executing the function, but there are much better ways to do that (such as returning an extra value that indicates if/where an error occurred).
A bare raise statement re-raises the last caught exception. https://docs.python.org/2/tutorial/errors.html#raising-exceptions
Returning and raising are mutually exclusive.
Raising SystemExit will end the script. A few cleanup routines get to run, and if the caller really, really wants to, they can catch the SystemExit and cancel it, but mostly, you can think of it as stopping execution right there. The caller will never get a chance to see a return value or do anything meaningful with it.
Returning means you want the script to continue. Continuing might mean having the caller raise SystemExit, or it might mean ignoring the error, or it might mean something else. Whatever it means is up to you, as you're the one writing the code.
Finally, are you sure you should be handling this error at all? Catching an exception only to turn it into a system shutdown may not be the most useful behavior. It's not a user-friendly way to deal with problems, and it hides all the useful debugging information you'd get from a stack trace.
You can raise an error with a 'returning_value' argument to be used after the calling.
Another pythonic answer to your problem could be to make use of the error arguments in the raise and then, in your call manage the error to get the value, convert it from string and get your 'return-ish'.
def your_f():
try:
some_io_thingy_ok()
return 1
except IOError:
raise SystemExit("FOOBAR", 0)
try:
my_returning_value = your_f()
except SystemExit as err:
my_returning_value = err.args[1]
print(my_returning_value)
From Python 3 docs :
When an exception occurs, it may have an associated value, also known as the exception’s argument. The presence and type of the argument depend on the exception type.
The except clause may specify a variable after the exception name. The variable is bound to an exception instance with the arguments stored in instance.args. For convenience, the exception instance defines str() so the arguments can be printed directly without having to reference .args. One may also instantiate an exception first before raising it and add any attributes to it as desired.