Consider the following code
def do_some_stuff(x)
try:
val = f(x)
except NameError, e:
val = False
return va;
Now of course if that happens once or twice in a piece of code it's not a big deal. However in a large project this can happen over and over again.
Then you have another issue. Consider the common Django pattern:
try:
x = SomeModel.objects.get(foo=bar)
except SomeModel.DoesNotExist:
x = g(bar)
This ends up showing up over and over again in any sufficiently large Django app.
One of the beauties of Python is that is very usable for functional patterns fure to the first class nature of functions. This means we ca get rid of all of this boilerplate by pulling an idiom from functional languages - specifically maybe
.
Don't worry, I'm not going full on monad on you here, just a simple function to handle this elegantly.
def maybe(f, default = None, exceptions = (Exception,) ):
def _maybe(*args, **kwargs):
try:
return f(*args, **kwargs)
except exceptions:
if callable(default):
return default(*args, **kwargs)
else:
return default
return _maybe
#as a decorator
def perhaps(default = None, exceptions = (Exception,)):
def wraps(f):
return maybe(f,default,exceptions)
return wraps
Now, let's look against at our two use cases:
def do_some_stuff(x)
try:
val = f(x)
except NameError, e:
val = False
return val
becomes
@perhaps(False, NameError)
def do_some_stuff(x): f(x)
and
try:
x = SomeModel.objects.get(foo=bar)
except SomeModel.DoesNotExist, KeyError:
x = g(foo=bar)
becomes
f = maybe(SomeModel.objects.get, g, (SomeModel.DoesNotExist, KeyError))
x = f(foo=bar)
Remember, with great power comes great responsibility.
I like it. Let's try it out!