Suppose I have the following custom exception.
class CustomException(TypeError):
def __init__(message, code):
super().__init__(f'{code}: message')
self.code = code
How does python know when to catch my exception in the following code?
try:
x = doSomething(a, b, c)
except CustomException:
raise
when I implement the doSomething() function, must it explicitly throw a CustomException in order for it to be caught? Like, for built-in exception classes, code can throw an exception like a KeyError and we don't have to explicitly say raise KeyError whenever we do something with a dictionary.
Any code that raises an exception has done so explicitly, including KeyError. No special handling is needed for custom exceptions versus the built-in types. A try...except can only catch an exception if one has been raised by code executed by any statement inside the try. This includes any further function calls, calls chain together into a callstack.
In the following example
>>> d = {}
>>> d['foo']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'foo'
the KeyError doesn't spring out of nothingness, the Python dict implementation raises that exception explicitly. This may not always be obvious because native code (code implemented in C here) doesn't show up in the Python traceback.
For the d[...] subscription operation the dict_subscript() function calls _PyErr_SetKeyError(key);, a helper function that uses PyErr_SetObject(), the C equivalent of raise to raise the KeyError(key) exception.
Catching exceptions works the same for all exception types, custom exceptions are not special here. When an exception is raised the normal code flow is interrupted, and the callstack is unwound until an active try statement is encountered, and then any exception handlers are tested, in order of definition in the source code, with isinstance(active_exception, ExceptionClassBeingHandled).
If nothing catches the exception, Python continues unwinding the callstack until it reaches the last stack frame, at which point Python would exit with a stack trace if nothing caught the exception.
Related
I have just learned about exception chaining in Python. What I see is, that there are two ways - inplicit and explicit. If I don't do anything special and raise caught exception. Py will automatically save info about previous exception (if I understand it correctly to __context__ attribute). If the second exception is not caught I'll get nice stack containing both exceptions. I can also make this explicitly using from keyword. In this case only difference I see is, that instead of using __context__ Py uses __cause__. And stack message changes from During handling of the above exception, another exception occurred: to The above exception was the direct cause of the following exception:. So why is there explicit exception chaining? If I don't do anything, I'll get the same thing.
In Java, getting the message of an exception is as easy as always calling a certain method.
But in Python, it seems to be impossible. Sometimes it works by doing this:
try:
# Code
pass
except Exception as e:
print(e.message)
But sometimes capturing an exception like that ends up by raising another exception because the message attribute doesn't exist. Ironically sad. Trying to control a error produces another one...
Sometimes it works by doing this:
print(e.msg)
But sometimes it also raises missing attribute exception.
Sometimes this works as well:
print(str(e))
But sometimes it prints an empty string so it is simply useless.
I've even heard that it depends on the library you're using, on the concrete Exception implementation. That seems really stupid for me. How can I handle an error for printing what has happened if I never know what attributes does it have for retrieving the error message?
But sometimes it prints an empty string so it is simply useless.
Yeah, that's what happens when someone raises an exception without a message. Blame authors (of the lib you are using) for that.
Generally you can use repr which is supposed to be unambiguous and if not overriden contains at least information about the exception's type:
try:
0/0
except Exception as exc:
print(repr(exc))
raise
If you need whole traceback you can use
import traceback
try:
0/0
except Exception:
print(traceback.format_exc())
raise
I am writing a class in Python and part of the code deals with a server. Therefore I need to deal with exceptions like ExpiredSession or ConnectionError.
Instead of writing exception handling code for every try/except block, I have a single function in the class to deal with the exceptions. something like this (inside the class definition)
def job_a(self):
try:
do something
except Exception as e:
#maybe print the error on screen.
self.exception_handling(e)
def job_b(self):
try:
do something else
except Exception as e:
#maybe print the error on screen.
self.exception_handling(e)
def exception_handling(self,e):
if isInstanceOf(e,ExpiredSession):
#deal with expired session.
self.reconnect()
if isInstanceOf(e,ConnectionError):
#deal with connection error
else:
#other exceptions
I am not sure if this kind of code would cause any problem because I haven't seen code do this. like, possible memory leak? (Now I notice the memory usage grows(though slowly) when I have more and more error/exception and eventually I have to restart the process before it eats all my memories). Not sure this is the cause.
Is it a good practice to pass exceptions to a single function?
This is a good use case for a context manager. You can see some examples of using context managers for error handling here. The contextmanager decorator allows you to write context managers concisely in the form of a single function. Here's a simple example:
class Foo(object):
def meth1(self):
with self.errorHandler():
1/0
def meth2(self):
with self.errorHandler():
2 + ""
def meth3(self):
with self.errorHandler():
# an unhandled ("unexpected") kind of exception
""[3]
#contextlib.contextmanager
def errorHandler(self):
try:
yield
except TypeError:
print "A TypeError occurred"
except ZeroDivisionError:
print "Divide by zero occurred"
Then:
>>> x = Foo()
>>> x.meth1()
Divide by zero occurred
>>> x.meth2()
A TypeError occurred
The with statement allows you to "offload" the error handling into a separate function where you catch the exceptions and do what you like with them. In your "real" functions (i.e., the functions that do the work but may raise the exceptions), you just need a with statement instead of an entire block of complicated try/except statements.
An additional advantage of this approach is that if an unforeseen exception is raised, it will propagate up normally with no extra effort:
>>> x.meth3()
Traceback (most recent call last):
File "<pyshell#394>", line 1, in <module>
x.meth3()
File "<pyshell#389>", line 12, in meth3
""[3]
IndexError: string index out of range
In your proposed solution, on the other hand, the exception is already caught in each function and the actual exception object is passed the handler. If the handler gets an unexpected error, it would have to manually reraise it (and can't even use a bare raise to do so). Using a context manager, unexpected exceptions have their ordinary behavior with no extra work required.
In python is it true that except Exception as ex or except BaseException as ex is the the same as except: but you get a reference to the exception?
From what I understand BaseException is the newer default catch-all.
Aside from that why would you ever want just an except: clause?
The difference between the three is:
bare except catches everything, including system-exiting things like KeyboardInterrupt;
except Exception[ as ex] will catch any subclass of Exception, which should be all your user-defined exceptions and everything built-in that is non-system-exiting; and
except BaseException[ as ex] will, like bare except, catch absolutely everything.
Generally, I would recommend using 2. (ideally as a fallback, after you have caught specific/"expected" errors), as this allows those system-exiting exceptions to percolate up to the top level. As you say, the as ex part for 2. and 3. lets you inspect the error while handling it.
There is a useful article on "the evils of except" here.
There are several differences, apart from Pokémon exception handling* being a bad idea.
Neither except Exception: nor except BaseException: will catch old-style class exceptions (Python 2 only):
>>> class Foo(): pass
...
>>> try:
... raise Foo()
... except Exception:
... print 'Caught'
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
__main__.Foo: <__main__.Foo instance at 0x10ef566c8>
>>> try:
... raise Foo()
... except BaseException:
... print 'Caught'
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
__main__.Foo: <__main__.Foo instance at 0x10ef56680>
>>> try:
... raise Foo()
... except:
... print 'Caught'
...
Caught
because the old-style object is not derived from BaseException or Exception. This is a good reason to never use custom exceptions that do not derive from Exception, in any case.
Next, there are three exceptions that derive from BaseException, but not from Exception; in most cases you don't want to catch those. SystemExit, KeyboardInterrupt and GeneratorExit are not exceptions you would want to catch in the normal course of exception handling. If you use except BaseException: you do catch these, except Exception will not.
* Pokémon exception handling because you gotta catch em all.
If you truly do not care what the reason or message of the failure was, you can use a bare except:. Sometimes this is useful if you are trying to access some functionality which may or may not be present or working, and if it fails you plan to degrade gracefully to some other code path. In that case, what the error type or string was does not affect what you're going to do.
It's not quite the case, no.
If you have a look at the Python documentation on built-in exceptions (specifically this bit) you see what exceptions inherit from where. If you use raw except: it will catch every exception thrown which even includes KeyboardInterrupt which you almost certainly don't want to catch; the same will happen if you catch BaseException with except BaseException as exp: since all exceptions inherit from it.
If you want to catch all program runtime exceptions it's proper to use except Exception as exp: since it won't catch the type of exceptions that you want to end the program (like KeyboardInterrupt).
Now, people will tell you it's a bad idea to catch all exceptions in this way, and generally they're right; but if for instance you have a program processing a large batch of data you may rightfully want it to not exit in case of an exception. So long as you handle the exception properly (ie, log it and make sure the user sees an exception has occurred) but never just pass; if your program produces errors you're unaware of, it will do strange things indeed!
Aside from that why would you ever want just an except: clause?
Short answer: You don't want that.
Longer answer: Using a bare except: takes away the ability to distinguish between exceptions, and even getting a hand on the exception object is a bit harder. So you normally always use the form except ExceptionType as e:.
The documentation for the raise statement with no arguments says
If no expressions are present, raise re-raises the last exception that was active in the current scope.
I used to think that meant that the current function had to be executing an except clause. After reading this question and experimenting a little, I think it means that any function on the stack has to be executing an except clause, but I'm not sure. Also, I've realized I have no idea how the stack trace works with a no-arg raise:
def f():
try:
raise Exception
except:
g()
def g():
raise
f()
produces
Traceback (most recent call last):
File "foo", line 10, in <module>
f()
File "foo", line 5, in f
g()
File "foo", line 3, in f
raise Exception
Exception
That doesn't look like the stack at the time of the initial raise, or the stack at the time of the re-raise, or the concatenation of both stacks, or anything I can make sense of.
Am I right about a no-arg raise looking for any function on the stack executing an except clause? Also, how does the stack trace work on a reraise?
When you raise without arguments, the interpreter looks for the last exception raised and handled. It then acts the same as if you used raise with the most recent exception type, value and traceback.
This is stored in the interpreter state for the current thread, and the same information can be retrieved using sys.exc_info(). By 'handled' I mean that an except clause caught the exception. Quoting the try statement documentation:
Before an except clause’s suite is executed, details about the exception are assigned to three variables in the sys module: sys.exc_type receives the object identifying the exception; sys.exc_value receives the exception’s parameter; sys.exc_traceback receives a traceback object (see section The standard type hierarchy identifying the point in the program where the exception occurred. These details are also available through the sys.exc_info() function, which returns a tuple (exc_type, exc_value, exc_traceback).
See the implemenation notes in the Python evaluation loop (C code), specifically:
The second
bullet was for backwards compatibility: it was (and is) common to
have a function that is called when an exception is caught, and to
have that function access the caught exception via sys.exc_ZZZ.
(Example: traceback.print_exc()).
The traceback reflects how you came to the re-raise accurately. It is the current stack (line 10 calling f(), line 5 calling g()) plus the original location of the exception raised: line 3.
It turns out Python uses a surprising way of building tracebacks. Rather than building the whole stack trace on exception creation (like Java) or when an exception is raised (like I used to think), Python builds up a partial traceback one frame at a time as the exception bubbles up.
Every time an exception bubbles up to a new stack frame, as well as when an exception is raised with the one-argument form of raise (or the two-argument form, on Python 2), the Python bytecode interpreter loop executes PyTraceback_Here to add a new head to the linked list of traceback objects representing the stack trace. (0-argument raise, and 3-argument raise on Python 2, skip this step.)
Python maintains a per-thread stack of exceptions (and tracebacks) suspended by except and finally blocks that haven't finished executing. 0-argument raise restores the exception (and traceback) represented by the top entry on this stack, even if the except or finally is in a different function.
When f executes its raise:
raise Exception
Python builds a traceback corresponding to just that line:
File "foo", line 3, in f
raise Exception
When g executes 0-argument raise, this traceback is restored, but no entry is added for the 0-argument raise line.
Afterwards, as the exception bubbles up through the rest of the stack, entries for the g() and f() calls are added to the stack trace, resulting in the final stack trace that gets displayed:
Traceback (most recent call last):
File "foo", line 10, in <module>
f()
File "foo", line 5, in f
g()
File "foo", line 3, in f
raise Exception
Exception
The following piece of code might help you understand how the raise keyword works:
def fun(n):
try:
return 0 / n
except:
print('an exception raised:')
raise
try:
fun(0)
except:
print('cannot divide by zero')
try:
fun('0')
except:
print('cannot divide by a string')