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

Reply via email to