How to pop the interpreter's stack?

Steven D'Aprano steve+comp.lang.python at pearwood.info
Wed Dec 22 22:22:40 EST 2010


On Wed, 22 Dec 2010 13:53:20 -0800, Carl Banks wrote:

> On Dec 22, 8:52 am, kj <no.em... at please.post> wrote:
>> In <mailman.65.1292517591.6505.python-l... at python.org> Robert Kern
>> <robert.k... at gmail.com> writes:
>>
>> >Obfuscating the location that an exception gets raised prevents a lot
>> >of debugging...
>>
>> The Python interpreter does a lot of that "obfuscation" already, and I
>> find the resulting tracebacks more useful for it.
>>
>> An error message is only useful to a given audience if that audience
>> can use the information in the message to modify what they are doing to
>> avoid the error.
> 
> So when the audience files a bug report it's not useful for them to
> include the whole traceback?


Well, given the type of error KJ has been discussing, no, it isn't useful.

    Fault: function raises documented exception when passed input that
    is documented as being invalid

    What steps will reproduce the problem?
    1. call the function with invalid input
    2. read the exception that is raised
    3. note that it is the same exception as documented

    What is the expected output? What do you see instead?

    Excepted somebody to hit me on the back of the head and tell me 
    not to call the function with invalid input. Instead I just got 
    an exception.


You seem to have completely missed that there will be no bug report, 
because this isn't a bug. (Or if it is a bug, the bug is elsewhere, 
external to the function that raises the exception.) It is part of the 
promised API. The fact that the exception is generated deep down some 
chain of function calls is irrelevant.

The analogy is this: imagine a function that delegates processing of the 
return result to different subroutines:

def func(arg):
    if arg > 0:
        return _inner1(arg)
    else:
        return _inner2(arg)


This is entirely irrelevant to the caller. When they receive the return 
result from calling func(), they have no way of knowing where the result 
came from, and wouldn't care even if they could. Return results hide 
information about where the result was calculated, as they should. Why 
shouldn't deliberate, explicit, documented exceptions be treated the same?

Tracebacks expose the implementation details of where the exception was 
generated. This is the right behaviour if the exception is unexpected -- 
a bug internal to func -- since you need knowledge of the implementation 
of func in order to fix the unexpected exception. So far so good -- we 
accept that Python's behaviour under these circumstances is correct.

But this is not the right behaviour when the exception is expected, e.g. 
an explicitly raised exception in response to an invalid argument. In 
this case, the traceback exposes internal details of no possible use to 
the caller. What does the caller care if func() delegates (e.g.) input 
checking to a subroutine? The subroutine is an irrelevant implementation 
detail. The exception is promised output of the function, just as much so 
as if it were a return value.

Consider the principle that exceptions should be dealt with as close as 
possible to the actual source of the problem:

>>> f('good input')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
  File "<stdin>", line 2, in g
  File "<stdin>", line 2, in h
  File "<stdin>", line 2, in i
  File "<stdin>", line 2, in j
  File "<stdin>", line 2, in k    <=== error occurs here, and shown here
ValueError


But now consider the scenario where the error is not internal to f, but 
external. The deeper down the stack trace you go, the further away from 
the source of the error you get. The stack trace now obscures the source 
of the error, rather than illuminating it:

>>> f('bad input')    <=== error occurs here
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
  File "<stdin>", line 2, in g
  File "<stdin>", line 2, in h
  File "<stdin>", line 2, in i
  File "<stdin>", line 2, in j
  File "<stdin>", line 2, in k    <=== far from the source of error
ValueError


There's no point in inspecting function k for a bug when the problem has 
nothing to do with k. The problem is that the input fails to match the 
pre-conditions for f. From the perspective of the caller, the error has 
nothing to do with k, k is a meaningless implementation detail, and the 
source of the error is the mismatch between the input and what f expects. 
And so by the principle of dealing with exceptions as close as possible 
to the source of the error, we should desire this traceback instead:


>>> f('bad input')    <=== error occurs here
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f    <=== matches where the error occurs
ValueError


In the absence of any practical way for function f to know whether an 
arbitrary exception in a subroutine is a bug or not, the least-worst 
decision is Python's current behaviour: take the conservative, risk-
adverse path and assume the worst, treat the exception as a bug in the 
subroutine, and expose the entire stack trace.

But, I suggest, we can do better using the usual Python strategy of 
implementing sensible default behaviour but allowing objects to customize 
themselves. Objects can declare themselves to be instances of some other 
class, or manipulate what names are reported by dir. Why shouldn't a 
function deliberately and explicitly take ownership of an exception 
raised by a subroutine?

There should be a mechanism for Python functions to distinguish between 
unexpected exceptions (commonly known as "bugs"), which should be 
reported as coming from wherever they come from, and documented, expected 
exceptions, which should be reported as coming from the function 
regardless of how deep the function call stack really is.



-- 
Steven



More information about the Python-list mailing list