I’ve complain in recent months that my knowledge of Python has stagnated. I look at code I wrote six months ago and I still think its well-written code and there isn’t much I would change. It is well-written code, but in previous lives, my definition of good code would evolve.
Possibly due to my early training in Java, I’ve always been quite cautious when raising exceptions. In Java, exceptions are a curse. The overhead of maintaining them and listing which ones can be caught or thrown from a given call-stack is time-consuming and of dubious benefit.
In the past, I’ve read the word ‘Exception’ as meaning ‘an exceptional circumstance I didn’t anticipate’. I don’t raise exceptions often unless I’m writing a library and want to communicate an exceptional circumstance the calling system may not have expected.
Excluded from this definition are ‘error conditions I did anticipate’, such as validation errors or permission checks. When I expect such errors, I tend to include them in the return values. If you’re a python coder you might think I mean this:
## BAD CODE ## result = some_function_with_error() if type(result): == tuple: error, message = result # handle error
But I would never do this. Checking the type of an object in Python is a sure sign you’re doing something wrong, probably very wrong. Either that or you’re writing some kind of introspective framework. I’ve seen code where people return an object in the normal case and a string if its an error. They then check if type(response) == str. This will fail when you ultimately have to switch to unicode, and its going to make your Python 3 porting attempts very confusing. Don’t do it.
However, I frequently use this idiom:
isadmitted = auth.check_privilege() if isadmitted != True: return isadmitted
In this case, check_privilege is returning either True or an error string. I used to think this was good code, but its actually doing an implicit type check. if you have to write ‘x != True’ instead of ‘not x’, its not proper duck-typing. While its unlikely that boolean will ever have values other than ‘True’ and ‘False’, its highly probable that your function will one-day want to return something else.
That last bit is based on real code I decided to refactor today. I’ve expanded my definition of ‘Exception’ to ‘anything that is not the normal return value of a function’. Now I have code that looks like this:
try: response = self.process_post(session) except (ValidationError, InsufficientPrivledgesError), e: session.rollback() response = e.response else: session.commit() finally: session.close() return response
I’ve wondered why so much attention was paid to Python’s try…except handlers in recent releases. Now I know — its because they expect Python coders to use them. Two months ago if I’d written an exception handler that included all of except, else, and finally, I’d have thought I was doing something wrong. My new understanding (notice I don’t say ‘corrected understanding’) is that this is how such code should look.
The good news is: every time I look at code I wrote six months ago and see something that uses an if statement to check a return value for an error condition, I’m going to be itching to rewrite it. My understanding of what good Python code should be has evolved. I’m back!