Consider a case in Python where one uses getattr
to either dynamically fetch a method or a value.
def message(obj, msg, *args, **kwargs):
result = getattr(obj, msg, None)
if result is not None:
try:
return result(*args, **kwargs)
except TypeError:
return result
...only, hang on -- that's not really very good behavior. Even if this is a bad getattr
call, None is implicitly returned anyway -- that's not necessarily great behavior for this kind of function.
In the interest of determining a solid "believe me, ya don't want to return this" value in the face of no good and decent sentinels (that I knew of anyway), I considered setting the default for getattr
to a function that raises an exception. In this way a bad search should always be obvious and caught, unless the 'other guy' decides to be cute and make this useless sentinel an attribute.
class _BadMessageException(Exception):
pass
def _did_not_find(*args, **kwargs):
raise BadMessageException
def _raise_right_exception(msg, obj):
if not isinstance(msg, basestring):
raise TypeError("Message '{}' was not a string".format(msg))
else:
raise AttributeError("Bad message '{}' sent to object '{}'".format(msg, obj))
In this way, the message is always at least on the up-and-up when it returns None, because it found a None where you asked it to look. It also then raises the exception you'd expect: AttributeError for an object with no such method/ivar, TypeError for too many args passed, etc etc. EDIT: Naturally, I post the wrong code snippet the first time around. Here's the corrected function.
def message(obj, msg, *args, **kwargs):
result = getattr(obj, msg, _did_not_find)
try:
return result(*args, **kwargs)
except TypeError:
if not args or kwargs:
return result
else:
_raise_right_exception(msg, obj)
except _BadMessageException:
_raise_right_exception(msg, obj)
It feels like a lot of extra code just to make sure this fails in the right way. A function that raises an exception which is just a McGuffin to raise the preferred exception, just to appease the eafp
demigods... Hrrm.
Is there a simpler syntax for declaring a valid 'fail' sentinel, either in this or other situations where the return value is not known or guaranteed?