This post originated from an RSS feed registered with Python Buzz
by Ian Bicking.
Original Post: Python nit, chapter 2
Feed Title: Ian Bicking
Feed URL: http://www.ianbicking.org/feeds/atom.xml
Feed Description: Thoughts on Python and Programming.
Well, since my last complaint was over two months ago, I guess I'm
fairly happy. Now I'll give something of an anti-poka-yoke
Who among us has not started with a function like:
def munger(v):
v = mungify(v)
return doublemungify(v)
Then realized doublemungify goes too far, and changes it to:
def munger(v):
v = mungify(v)
But, whoops, now the function returns None. There are many ways to
encounter this common bug, to unintentionally return None by not
returning anything at all. Maybe one of the execution paths doesn't
have a return, or you just forgot about the return at some point. It
happens to me often -- but the worst part is that it's not quickly
detected. Often I'll collect the results of munger, and the None
will only cause a problem sometime later when it's unclear where it
came from. Python nit: functions return None when you don't want them
to return anything -- None is a oft-used real value, not some
this-function-returns-nothing value.
It would be better if functions didn't return any value when no return
(or a bare return) when encountered. So using the second munger
form in an expression would cause an immediate (and informative)
exception. In other words, distinguishing functions from procedures.
It seems like there's some sort of elegance in not making that
distinction, in keeping these objects uniform. Plus Pascal
distinguishes the two forms, and we all hate Pascal. But really, what
use can there be to the implicit returning of None?
Well, one use, usually in subclassing. Supposing munger is a
method, we might do:
class SafeGrinder(Grinder):
def munger(self, v):
if not self.isSafe(v):
raise ValueError, "Unsafe: %s" % v
return Grinder.munger(self, v)
Because we can always treat functions as, well, functions, we can
blindly pass values through. Without this we'd need some sort of
special syntax to deal with no return value, or else a way to test
whether the object was a function or procedure. The test would be
annoying, and would require some static declaration (e.g., two kinds
of def). The static declaration would make that pass-through
function even harder, because it wouldn't be sufficient to just test
whether the function returned a value, we'd also have to declare
whether our pass-through code was a function or procedure.
Some other syntax might be better, like (Grinder.munger(self, v)
ifprocedure None), which would evaluate to None if
Grinder.munger was a procedure, or the return value if not. Then
you might do something like:
class OneOff: pass
class SafeGrinder(Grinder):
def munger(self, v):
if not self.isSafe(v): raise ValueError, "Unsafe: %s" % v
result = Grinder.munger(self, v) ifprocedure OneOff
if result is not OneOff: return result
Or maybe even better:
class SafeGrinder(Grinder):
def munger(self, v):
if not self.isSafe(v): raise ValueError, "Unsafe: %s" % v
try:
return Grinder.munger(self, v)
except NoReturnValue:
pass # and return no value
It can be a little complicated, though only for this one case, while
other more common cases would be less error-prone. But it's not
really going to happen in this late stage. Oh well.