I’ve been going over a bunch of old code lately and picking out some awful habits of mine. Today I want to talk about one that unfortunately is also prevalent throughout the 1st edition of book: sloppy exception handling.
Let’s first look at a short (somewhat contrived) example to demonstrate a couple problems.
import maya.cmds as cmds def make_nodes(node_type_name, amount): """ Make the specified amount of the specified node. @param node_type_name: The name of the node type to create. @param amount: The number of nodes to make. """ current_selection = cmds.ls(sl=True) try: for i in range(amount): cmds.createNode(node_type_name) except: raise try: cmds.select(current_selection) except: pass
The first problem here concerns the first try-except clause. Namely: why does it exist? If anything in the try block fails (either the loop or the createNode call), it will raise an exception. Wrapping this code in a try clause is useless in this case, because we don’t actually change any behavior if an exception happens to be raised. The exception is just tossed out to the calling context.
The second problem is that both of these try-except clauses use what I have grown to call Pokémon Exception Handling: they gotta catch ’em all! Unlike the first try-except clause, the second one at least has a point here. (For example, if the selection list is empty when this function is called, resetting the selection would ordinarily raise a TypeError.) However, the PEP style guide recommends against this type of bare except clause (and suggests minimally specifying Exception). Why?
Imagine this function did something more substantial to edit the scene. Suppose it possibly deletes some nodes as well as creates them. If you simply throw in a bare except clause, you may catch errors you do not actually want to, which can lead to sloppy programming. For example, while selecting an empty list throws a TypeError, trying to select an object that does not exist throws a ValueError. In this case, we want to silently trap and ignore the TypeError, but NOT the ValueError, as we anticipate the former, but not the latter. If we built a set of tests for this function, we might totally miss scenarios in which a ValueError is raised. In short, do not put in an except clause unless you anticipate a specific exception of some kind and plan to actually do something about it.
Finally, you should strive to try the minimum amount of code possible at any one time. It is possible some errors can be raised in multiple ways. Since the PEP discusses this point, I won’t go into detail, but you can imagine there may be some cases where you want to catch one TypeError, but not another, in which case they should not be wrapped in the same try clause.
I want to end this post by talking about the API command examples in the book. The book notes that when parsing the command’s arguments by constructing an MArgDatabase, simply raising the exception throws out a really non-descriptive RuntimeError. The book errantly advises that this just be passed over instead of raised, because the command engine would display information about the problem (at least when the command is invoked from MEL). But what about when the command is invoked from Python? Obviously we want it to throw some kind of exception, and probably not just a really low-level RuntimeError. The trouble here of course is that, because you cannot initialize an MArgDatabase or an MArgParser, you can’t really tell what exactly is wrong. In this case, the safest thing to do, in my view, is to raise a TypeError, which can occur with either an invalid flag, or with a flag argument of the wrong type. While you can’t tell the user exactly what is wrong, it is still better than raising the bult-in RuntimeError, and of course infinitely preferable to raising no exception at all!