Having too many custom exceptions on a project can be a pain, but a few choices ones are really nice. The problem is that in complex libraries having to import both functions and exceptions becomes a drag. To mitigate having to remember to import custom exceptions, this is a handy pattern you can use in a project and can be done on both functions and classes.
This works because Python functions are first-class objects. They can be passed around as things, and in this case, have things assigned to them.
# logic.py class DoesNotCompute(Exception): """ Easy to understand naming conventions work best! """ pass def this_function(x): """ This function only works on numbers.""" try: return x ** x except TypeError: raise DoesNotCompute # Assign DoesNotCompute exception to this_function this_function.DoesNotCompute = DoesNotCompute
Now I can import the function, and it won't just through
DoesNotCompute exceptions, it will also carry the function along with
>>> from logic import this_function >>> this_function(5) 3125 >>> this_function(4.5) 869.8739233809259 >>> this_function('will throw an error.') Traceback (most recent call last): File "<input>", line 1, in <module> File "logic.py", line 10, in this_function raise DoesNotCompute DoesNotCompute
Alright, that doesn't seem like much, but let's add in some exception handling:
>>> try: ... this_function('is an example') ... except this_function.DoesNotCompute: ... print('See what attaching custom exceptions to functions can do?') ... ... See what attaching custom exceptions to functions can do?
All we have to do is enhance our existing logic.py file by adding
# logic.py class DoesNotCompute(Exception): """ Easy to understand naming conventions work best! """ pass # removed the function example for clarity class ThisClass(object): # Since the DoesNotCompute exception exists, let's just assign it # as an attribute of ThisClass DoesNotCompute = DoesNotCompute def this_method(self, x): """ This method only works on numbers.""" try: return x ** x except TypeError: raise DoesNotCompute
Now to demonstrate in the shell (Python REPL for the semantic purists):
>>> from t import ThisClass >>> this_class = ThisClass() >>> this_class.this_method(3.3) 51.415729444066585 >>> this_class.this_method('Jack Diederich warned against custom exceptions') Traceback (most recent call last): File "<input>", line 1, in <module> File "logic.py", line 24, in this_method raise DoesNotCompute DoesNotCompute >>> try: ... this_class.this_method('I need to write a follow-up on my OAuth post') ... except ThisClass.DoesNotCompute: ... print('Waiting to see how the OAuth stuff pans out') ... ... Waiting to see how the OAuth stuff pans out
Rather than use this trick all over the place, considering using it in a
few places to powerful effect. For example,
Django uses it only in a few places, and
publicly only on
MyModelClass.MultipleObjectsReturned. By limiting Django's use of
this technique, Django libraries are that much easier to comprehend. In
this case, less complexity means more.
I say this because this pattern lends itself to creating custom exceptions to the point of effectively replacing Python's stock exceptions with your own. This makes for harder-to-maintain and harder-to-learn projects.
Not that I've ever done that. Ahem.
Published: 2012-08-02 09:30
Content Copyright © 2012-2018 Daniel Greenfeld. Proudly harnessed by Mountain, powered by Flask, and rendered by Frozen Flask, all of which take great advantage of Python.