On 12/30/2015 7:09 PM, Steven D'Aprano wrote:
I have a lot of functions that perform the same argument checking each time:
def spam(a, b):
if condition(a) or condition(b): raise TypeError
if other_condition(a) or something_else(b): raise ValueError
if whatever(a): raise SomethingError
...
def eggs(a, b):
if condition(a) or condition(b): raise TypeError
if other_condition(a) or something_else(b): raise ValueError
if whatever(a): raise SomethingError
...
Since the code is repeated, I naturally pull it out into a function:
def _validate(a, b):
if condition(a) or condition(b): raise TypeError
if other_condition(a) or something_else(b): raise ValueError
if whatever(a): raise SomethingError
def spam(a, b):
_validate(a, b)
...
def eggs(a, b):
_validate(a, b)
...
But when the argument checking fails, the traceback shows the error
occurring in _validate, not eggs or spam. (Naturally, since that is where
the exception is raised.) That makes the traceback more confusing than it
need be.
So I can change the raise to return in the _validate function:
def _validate(a, b):
if condition(a) or condition(b): return TypeError
if other_condition(a) or something_else(b): return ValueError
if whatever(a): return SomethingError
and then write spam and eggs like this:
def spam(a, b):
ex = _validate(a, b)
if ex is not None: raise ex
...
It's not much of a gain though. I save an irrelevant level in the traceback,
but only at the cost of an extra line of code everywhere I call the
argument checking function.
It is nicer than the similar standard idiom
try:
_validate(a, b)
except Exception as e:
raise e from None
If you could compute a reduced traceback, by copying one, then this
might work (based on Chris' idea.
def _validate(a, b):
ex = None
if condition(a) or condition(b): ex = TypeError
elif other_condition(a) or something_else(b): ex = ValueError
elif whatever(a): ex = SomethingError
if ex:
try: 1/0
except ZeroDivisionError as err: tb = err.__traceback__
tb = <copy of tb with this frame omitted>
raise ex.with_traceback(tb)
But suppose we allowed "raise None" to do nothing. Then I could rename
_validate to _if_error and write this:
def spam(a, b):
raise _if_error(a, b)
...
and have the benefits of "Don't Repeat Yourself" without the unnecessary,
and misleading, extra level in the traceback.
Obviously this doesn't work now, since raise None is an error, but if it did
work, what do you think?
Perhaps a bit too magical, but maybe not.
--
Terry Jan Reedy
--
https://mail.python.org/mailman/listinfo/python-list