Is this an appropriate way to define and use a Python exception? - python

I just want to make sure I'm doing this in the proper "pythonic" way - I want to make sure I've defind - and am using - this exception class correctly. Especially the eval(repr()) logic - it's mostly for cleanliness, I understand why you end up with quotes around the string repr() returns, but I don't like to log them.
class IPCClientError(Exception):
""" General IPC Client Exception class """
def __init__(self, value = "Unspecified error"):
self.val = value + ", see IPC client log for details."
def __str__(self):
return eval(repr(self.val))
When I raise the exception, I use something like:
raise IPCClientError("Socket error")
And then the calling method will have something like this:
except IPCClientError, exc:
self.log.error(str(exc))
return ERROR

eval(repr(self.val))
Eek, what are you trying to accomplish here? isn't self.val already supposed to be a string?
The way to avoid the quotes that repr attaches is not to use it in the first place.
If you're worried that the value passed to the constructor won't be a string, well - that will fail in the constructor (unicode quibbles notwithstanding) anyway and you'll just get a TypeError raised before your custom exception can be.
As for how you handle the exception, exception handling is kind of an art, and really not something that can be covered in this space...

(apart from the eval stuff already mentioned by others)
In your except statement, you should write except IPCClientError as exc: (notice the "as"), which is the newer, python 3 compatible way to do it. (the other syntax won't work anymore in python 3, the new one works in python 2.6 and higher)

Related

How do I catch exceptions after a "raise from" in python?

I have code that raises exceptions from other exceptions so that we can see details about eveything that went wrong. In the example below we include information about what we are processing and what specific thing in the processing went wrong.
def process(thing):
try:
process_widgets(thing)
except Exception as e:
raise CouldNotProcess(thing) from e
def process_widgets(thing):
for widget in get_widgets(thing):
raise CouldNotProcessWidget(widget)
def do_processing():
for thing in things_to_process():
process(thing)
I am trying to change this so that process_widgets can raise a specific type of exception and do_processing can change its behaviour based on this exception. However the raise from in process is masking this exception which makes this impossible. Is there a good to let do_processing know about what went wrong in process_widgets while also doing raise from.
Ideas:
Python 3.11 has exception groups. So perhaps there is a way of adding exceptions to group and catching them with the likely confusing except* syntax.
There is a dirty trick where I do raise e from CouldNoPorcess(thing) to get both the helpful logging.
Apparently internally exception chaining works by adding __cause__ property (forming a linked list) so I could manually look through causes in the top most exception to manually implement behaviour like except* with exception groups.
def raised_from(e, type):
while e is not None:
if isinstance(e, Specific):
return True
e = e.__cause__
return False
...
try:
do_processing()
except CouldNotProcess as e:
if raised_from(e, CouldNotProcessWidget):
do_stuff()
You see this pattern quite a lot with status_codes from http.
I could use logging rather than adding information to exceptions. This hides information from the exception handling code, but works for logging. I think this is the work around that I'll use at the moment.
It's noticeable that the PEP says that exception chaining isn't quite designed for adding information to exceptions.
Update
python 3.11 has an add_note method and notes property which can be used to add information - which works for some use cases.
For this use case exception groups might be the way to go, though I am concerned that this might be a little confusing.

Is it possible to catch an exception from outside code that is already catching it?

This is a hard question to phrase, but here's a stripped-down version of the situation. I'm using some library code that accepts a callback. It has its own error-handling, and raises an error if anything goes wrong while executing the callback.
class LibraryException(Exception):
pass
def library_function(callback, string):
try:
# (does other stuff here)
callback(string)
except:
raise LibraryException('The library code hit a problem.')
I'm using this code inside an input loop. I know of potential errors that could arise in my callback function, depending on values in the input. If that happens, I'd like to reprompt, after getting helpful feedback from its error message. I imagine it looking something like this:
class MyException(Exception):
pass
def my_callback(string):
raise MyException("Here's some specific info about my code hitting a problem.")
while True:
something = input('Enter something: ')
try:
library_function(my_callback, something)
except MyException as e:
print(e)
continue
Of course, this doesn't work, because MyException will be caught within library_function, which will raise its own (much less informative) Exception and halt the program.
The obvious thing to do would be to validate my input before calling library_function, but that's a circular problem, because parsing is what I'm using the library code for in the first place. (For the curious, it's Lark, but I don't think my question is specific enough to Lark to warrant cluttering it with all the specific details.)
One alternative would be to alter my code to catch any error (or at least the type of error the library generates), and directly print the inner error message:
def my_callback(string):
error_str = "Here's some specific info about my code hitting a problem."
print(error_str)
raise MyException(error_str)
while True:
something = input('Enter something: ')
try:
library_function(my_callback, something)
except LibraryException:
continue
But I see two issues with this. One is that I'm throwing a wide net, potentially catching and ignoring errors other than in the scope I'm aiming at. Beyond that, it just seems... inelegant, and unidiomatic, to print the error message, then throw the exception itself into the void. Plus the command line event loop is only for testing; eventually I plan to embed this in a GUI application, and without printed output, I'll still want to access and display the info about what went wrong.
What's the cleanest and most Pythonic way to achieve something like this?
There seems to be many ways to achieve what you want. Though, which one is more robust - I cannot find a clue about. I'll try to explain all the methods that seemed apparent to me. Perhaps you'll find one of them useful.
I'll be using the example code you provided to demonstrate these methods, here's a fresher on how it looks-
class MyException(Exception):
pass
def my_callback(string):
raise MyException("Here's some specific info about my code hitting a problem.")
def library_function(callback, string):
try:
# (does other stuff here)
callback(string)
except:
raise Exception('The library code hit a problem.')
The simplest approach - traceback.format_exc
import traceback
try:
library_function(my_callback, 'boo!')
except:
# NOTE: Remember to keep the `chain` parameter of `format_exc` set to `True` (default)
tb_info = traceback.format_exc()
This does not require much know-how about exceptions and stack traces themselves, nor does it require you to pass any special frame/traceback/exception to the library function. But look at what this returns (as in, the value of tb_info)-
'''
Traceback (most recent call last):
File "path/to/test.py", line 14, in library_function
callback(string)
File "path/to/test.py", line 9, in my_callback
raise MyException("Here's some specific info about my code hitting a problem.")
MyException: Here's some specific info about my code hitting a problem.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "path/to/test.py", line 19, in <module>
library_function(my_callback, 'boo!')
File "path/to/test.py", line 16, in library_function
raise Exception('The library code hit a problem.')
Exception: The library code hit a problem.
'''
That's a string, the same thing you'd see if you just let the exception happen without catching. Notice the exception chaining here, the exception at the top is the exception that happened prior to the exception at the bottom. You could parse out all the exception names-
import re
exception_list = re.findall(r'^(\w+): (\w+)', tb_info, flags=re.M)
With that, you'll get [('MyException', "Here's some specific info about my code hitting a problem"), ('Exception', 'The library code hit a problem')] in exception_list
Although this is the easiest way out, it's not very context aware. I mean, all you get are class names in string form. Regardless, if that is what suits your needs - I don't particularly see a problem with this.
The "robust" approach - recursing through __context__/__cause__
Python itself keeps track of the exception trace history, the exception currently at hand, the exception that caused this exception and so on. You can read about the intricate details of this concept in PEP 3134
Whether or not you go through the entirety of the PEP, I urge you to at least familiarize yourself with implicitly chained exceptions and explicitly chained exceptions. Perhaps this SO thread will be useful for that.
As a small refresher, raise ... from is for explicitly chaining exceptions. The method you show in your example, is implicit chaining
Now, you need to make a mental note - TracebackException#__cause__ is for explicitly chained exceptions and TracebackException#__context__ is for implicitly chained exceptions. Since your example uses implicit chaining, you can simply follow __context__ backwards and you'll reach MyException. In fact, since this is only one level of nesting, you'll reach it instantly!
import sys
import traceback
try:
library_function(my_callback, 'boo!')
except:
previous_exc = traceback.TracebackException(*sys.exc_info()).__context__
This first constructs the TracebackException from sys.exc_info. sys.exc_info returns a tuple of (exc_type, exc_value, exc_traceback) for the exception at hand (if any). Notice that those 3 values, in that specific order, are exactly what you need to construct TracebackException - so you can simply destructure it using * and pass it to the class constructor.
This returns a TracebackException object about the current exception. The exception that it is implicitly chained from is in __context__, the exception that it is explicitly chained from is in __cause__.
Note that both __cause__ and __context__ will return either a TracebackException object, or None (if you're at the end of the chain). This means, you can call __cause__/__context__ again on the return value and basically keep going till you reach the end of the chain.
Printing a TracebackException object just prints the message of the exception, if you want to get the class itself (the actual class, not a string), you can do .exc_type
print(previous_exc)
# prints "Here's some specific info about my code hitting a problem."
print(previous_exc.exc_type)
# prints <class '__main__.MyException'>
Here's an example of recursing through .__context__ and printing the types of all exceptions in the implicit chain. (You can do the same for .__cause__)
def classes_from_excs(exc: traceback.TracebackException):
print(exc.exc_type)
if not exc.__context__:
# chain exhausted
return
classes_from_excs(exc.__context__)
Let's use it!
try:
library_function(my_callback, 'boo!')
except:
classes_from_excs(traceback.TracebackException(*sys.exc_info()))
That will print-
<class 'Exception'>
<class '__main__.MyException'>
Once again, the point of this is to be context aware. Ideally, printing isn't the thing you'll want to do in a practical environment, you have the class objects themselves on your hands, with all the info!
NOTE: For implicitly chained exceptions, if an exception is explicitly suppressed, it'll be a bad day trying to recover the chain - regardless, you might give __supressed_context__ a shot.
The painful way - walking through traceback.walk_tb
This is probably the closest you can get to the low level stuff of exception handling. If you want to capture entire frames of information instead of just the exception classes and messages and such, you might find walk_tb useful....and a bit painful.
import traceback
try:
library_function(my_callback, 'foo')
except:
tb_gen = traceback.walk_tb(sys.exc_info()[2])
There is....entirely too much to discuss here. .walk_tb takes a traceback object, you may remember from the previous method that the 2nd index of the returned tuple from sys.exec_info is just that. It then returns a generator of tuples of frame object and int (Iterator[Tuple[FrameType, int]]).
These frame objects have all kinds of intricate information. Though, whether or not you'll actually find exactly what you're looking for, is another story. They may be complex, but they aren't exhaustive unless you play around with a lot of frame inspection. Regardless, this is what the frame objects represent.
What you do with the frames is upto you. They can be passed to many functions. You can pass the entire generator to StackSummary.extract to get framesummary objects, you can iterate through each frame to have a look at [0].f_locals (The [0] on Tuple[FrameType, int] returns the actual frame object) and so on.
for tb in tb_gen:
print(tb[0].f_locals)
That will give you a dict of the locals for each frame. Within the first tb from tb_gen, you'll see MyException as part of the locals....among a load of other stuff.
I have a creeping feeling I have overlooked some methods, most probably with inspect. But I hope the above methods will be good enough so that no one has to go through the jumble that is inspect :P
Chase's answer above is phenomenal. For completeness's sake, here's how I implemented their second approach in this situation. First, I made a function that can search the stack for the specified error type. Even though the chaining in my example is implicit, this should be able to follow implicit and/or explicit chaining:
import sys
import traceback
def find_exception_in_trace(exc_type):
"""Return latest exception of exc_type, or None if not present"""
tb = traceback.TracebackException(*sys.exc_info())
prev_exc = tb.__context__ or tb.__cause__
while prev_exc:
if prev_exc.exc_type == exc_type:
return prev_exc
prev_exc = prev_exc.__context__ or prev_exc.__cause__
return None
With that, it's as simple as:
while True:
something = input('Enter something: ')
try:
library_function(my_callback, something)
except LibraryException as exc:
if (my_exc := find_exception_in_trace(MyException)):
print(my_exc)
continue
raise exc
That way I can access my inner exception (and print it for now, although eventually I may do other things with it) and continue. But if my exception wasn't in there, I simply reraise whatever the library raised. Perfect!

What exception should I raise for "operation is currently invalid"?

Lets assume there is a method, and I want this method to be called only at certain conditions of a class variable, and otherwise I want to throw an exception:
def foo(self):
if not self.somecondition:
raise ????
else:
#do the thing it supposed to do
Now I am looking for a correct exception for that. I went through them all at python built-in Exceptions docs and found nothing suitable. The classic valueerror is not quite suitable because there is not necessarily a problem with the values but rather with the method being called in this
circumstances.
What exception should I use then? Do I need to create my own in this case or does python has a built in answer?

Custom exception codes and messages in Python 3.4

I'd like to have something like a custom error code/message database and use it when raising exceptions (in Python 3.4). So I did the following:
class RecipeError(Exception):
# Custom error codes
ERRBADFLAVORMIX = 1
ERRNOINGREDIENTS = ERRBADFLAVORMIX + 1
# Custom messages
ERRMSG = {ERRBADFLAVORMIX: "Bad flavor mix",
ERRNOINGREDIENTS: "No ingredients to mix"}
raise RecipeError(RecipeError.ERRMSG[RecipeError.ERRBADFLAVORMIX])
This works as expected, but the raise statement is just monstrous. Sure, I could have stored the values in a more compact way, but what I really want to know is: Can I just do something like raise RecipeError(code) and leave the work of getting the message to RecipeError?
Sure. Exception classes are just normal classes, so you can define your own __init__ that calls super appropriately:
class RecipeError(BaseException):
# existing stuff
def __init__(self, code):
super().__init__(self, RecipeError.ERRMSG[code])
You might also want to save the code:
class RecipeError(BaseException):
# existing stuff
def __init__(self, code):
msg = RecipeError.ERRMSG[code]
super().__init__(self, msg)
self.code, self.msg = code, msg
Take a look at the information stored in the standard library's exceptions (which are pretty decent in 3.4, although there are still more changes to comeā€¦) to see what kinds of things might be useful to stash.
Some side notes:
First, it may be better to use subclasses instead of error codes. For example, if someone wants to write code that catches an ERRBADFLAVORMIX but not an ERRNOINGREDIENTS, they have to do this:
try:
follow_recipe()
except RecipeError as e:
if e != RecipeError.ERRBADFLAVORMIX:
raise
print('Bad flavor, bad!')
Or, if you'd used subclasses:
try:
follow_recipe():
except BadFlavorRecipeError as e:
print('Bad flavor, bad!')
That's exactly why Python no longer has a monolithic OSError with an errno value that you have to switch on, and instead has separate subclasses like FileNotFoundError.
If you do want to use error codes, you might want to consider using an Enum, or maybe one of the fancier enum types on PyPI that make it easier to attach a custom string to each one.
You almost never want to inherit from BaseException, unless you're specifically trying to make sure your exception doesn't get caught.

Is it reasonable to declare an exception type for a single function?

Say I have this code:
def wait_for_x(timeout_at=None):
while condition_that_could_raise_exceptions
if timeout_at is not None and time.time() > timeout_at:
raise SOMEEXCEPTIONHERE
do_some_stuff()
try:
foo()
wait_for_x(timeout_at=time.time() + 10)
bar()
except SOMEEXCEPTIONHERE:
# report timeout, move on to something else
How do I pick an exception type SOMEEXCEPTIONHERE for the function? Is it reasonable to create a unique exception type for that function, so that there's no danger of condition_that_could_raise_exceptions raising the same exception type?
wait_for_x.Timeout = type('Timeout', (Exception,), {})
If distinguishing exceptions from wait_for_x from those from condition_that_could_raise_exceptions is important enough, then sure, define a new exception type. After all, the type is the main way of distinguishing different kinds of exceptions, and parsing the message tends to get messy pretty quickly.
Yes, you should certainly define your own exception class whenever none of the built-in exception types are appropriate. In some cases (say, if you're building some kind of HTML munger on top of LXML or BeautifulSoup) it might also be appropriate to use an exception from some other module.
Python Standard Library defines a lot of its own custom exceptions. It seems good practice to do that as well for personal modules or functions.

Categories

Resources