Skip to content

Instantly share code, notes, and snippets.

@rwilcox
Created July 18, 2010 11:49
Show Gist options
  • Save rwilcox/480347 to your computer and use it in GitHub Desktop.
Save rwilcox/480347 to your computer and use it in GitHub Desktop.
Python null handling pattern exploration
from __future__ import with_statement # not needed in 2.6 I think? WD-rpw 07-17-2010
# This file/gist explores nil handling in Python
#
# Hey look, there's a stackoverload question about the nill pattern in Python
# <http://stackoverflow.com/questions/2014105/null-pattern-in-python-underused>
#
# Where the vote seems to be that the exceptions are good (I agree),
# and the pattern should look like:
# if foo.bar is not None and foo.bar.baz:
# ....
# OR:
# try:
# foo.bar.baz
# except AttributeError, a:
# ...
#
# Which is OK, but a lot of code (and kinda smells). Especially if/when you have
# to make that check often in your problem domain
#
#
# So this file will look at the alternatives. (They certainly smell a little,
# I know.)
#
#
#
# Hmm, can a context manager solve our problems?
# ###################################################################################
from contextlib import contextmanager
@contextmanager
def out_nil_exceptions(squash_attribute_errors=False):
"""This generator will squash errors associated with
programming systems where:
#1: You might accidentally call a method on None
#2: You might accidentally mispell a method name,
- or it's been changed behind your back -
and want that stiffled (yes, a bad idea, and yes,
I've been on projects that have wanted this, on purpose)
TODO: maybe pass a logger instance here, so we can log this stuff... ???
"""
try:
yield
except AttributeError, e:
if "'NoneType' object has no attribute" in str(e):
raise StopIteration
else:
if ("object has no attribute "in str(e)) and squash_attribute_errors:
# TODO: this if doesn't work...
raise StopIteration
else:
raise e
######## AND HERE'S WHERE WE USE IT, LADIES AND GENTS:
res = None
a = None
with out_nil_exceptions():
res = a.bob()
print res or "was nil"
# SLIGHTLY ANNOYING THING - have to add an extra line each time
# it's only slightly better than nil checking yourself
# and since it's a generate you can't return stuff from it
# THE GOOD THING: I think it's better than Ruby/Rails alternative:
#
# fname = student.andand.first_name
# or
# fname = student.try(:first_name)
#
# The ruby alternative gets hairy when it's deeply chained... this Python solution doesn't
#
# student.andand.school.andand.principal.andand.firstname
#
# or
# student.try(:school).try(:principal).try(:firstname)
#
# Our Python alternative doesn't have nil checking baked into the code:
#
# principal_firstname = "Mr."
# with out_nil_exceptions():
# student.school.principal.first_name
#
# Which IS better, actually. And better that it will catch ALL the nil errors in the code
# instead of salting the chain with (is it nil here?), and maybe forgetting one.
student = None
fname = "The Default"
lname = "Default Last name"
with out_nil_exceptions():
fname = student.first_name
with out_nil_exceptions(squash_attribute_errors=True):
lname = student.last_name
print "fname = %s" % (fname or "nil")
print "lname = %s" % (lname or "nil")
############################################################################
# A: maybe. Still slightly ugly, but at least it's explicit that we are squashing errors
# I'm not convinced that a context manager does solve our problems, but it's... interesting
# and I'm out of time. Have a better idea? Comment away
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment