Short version:
Is there way to achieve in Python the same effect achieved by Perl's
Carp::carp
utility?
Long version (for those unfamiliar with Carp::carp
):
Suppose we are implementing some library API function (i.e., it is meant to be used by other programmers in their code), say spam
, and suppose that spam
includes some code to check the validity of the arguments passed to it. Of course, this code is supposed to raise an exception if any problem with these arguments is detected. Let's say that we want to make the associated error message and traceback as helpful as possible to someone debugging some client code.
Ideally, the last line of the traceback produced by this raised exception should pinpoint the "offending code", namely the line in the client code where spam
was called with invalid arguments.
Unfortunately, this is not what would happen, at least by default, using Python. Instead, the last line of the traceback will refer to somewhere in the internals of the library code, where the exception was actually raise
'd, which would be quite obscure to the intended audience of this particular traceback.
Example:
# spam.py (library code)
def spam(ham, eggs):
'''
Do something stupid with ham and eggs.
At least one of ham and eggs must be True.
'''
_validate_spam_args(ham, eggs)
return ham == eggs
def _validate_spam_args(ham, eggs):
if not (ham or eggs):
raise ValueError('if we had ham '
'we could have ham and eggs '
'(if we had eggs)')
# client.py (client code)
from spam import spam
x = spam(False, False)
When we run client.py
, we get:
% python client.py
Traceback (most recent call last):
File "client.py", line 3, in <module>
x = spam(False, False)
File "/home/jones/spam.py", line 7, in spam
_validate_spam_args(ham, eggs)
File "/home/jones/spam.py", line 12, in _validate_spam_args
raise ValueError('if we had ham '
ValueError: if we had ham we could have ham and eggs (if we had eggs)
whereas what we want would be closer to:
% python client.py
Traceback (most recent call last):
File "client.py", line 3, in <module>
x = spam(False, False)
ValueError: if we had ham we could have ham and eggs (if we had eggs)
...with the offending code (x = spam(False, False)
) as the last line of the traceback.
What we need is some way to report the error "from the perspective of the caller" (which is what Carp::carp
lets one do in Perl).
EDIT: Just to be clear, this question is not about LBYL vs EAFP, nor about preconditions or programming-by-contract. I am sorry if I gave this wrong impression. This question is about how to produce a traceback starting from a few (one, two) levels up the call stack.
EDIT2: Python's traceback
module is an obvious place to look for a Python-equivalent of Perl's Carp::carp
, but after studying it for some time I was not able to find any way to use it for what I want to do. FWIW, Perl's Carp::carp
allows fine-adjusting of the initial frame for the traceback by exposing the global (hence dynamically scoped) variable $Carp::CarpLevel
. Non-API library functions that may carp
-out, local
-ize and increase this variable on entry (e.g. local $Carp::CarpLevel += 1;
). I don't see anything even remotely like this Python's traceback
module. So, unless I missed something, any solution that uses Python's traceback
would have to take a rather different tack...