简洁版本:



长版(对于不熟悉Carp::carp的人):

假设我们正在实现某种库API函数(即,它打算由其他程序员在其代码中使用),例如spam,并假设spam包括一些代码来检查传递给它的参数的有效性。当然,如果检测到这些参数有任何问题,则应将此代码引发异常。假设我们要使相关的错误消息和回溯对调试某些客户端代码的人尽可能有用。

理想情况下,此引发的异常所产生的回溯的最后一行应指出“有问题的代码”,即客户端代码中用无效参数调用spam的行。

不幸的是,至少在默认情况下,使用Python不会发生这种情况。相反,回溯的最后一行将指向库代码内部的某个地方,该异常实际上是raise'd,这对于该特定回溯的目标读者而言是相当模糊的。

例:

# 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)

当我们运行client.py时,我们得到:
% 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)

而我们想要的则更接近:
% 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)

...将有问题的代码(x = spam(False, False))作为回溯的最后一行。

我们需要某种方法来“从调用者的 Angular ”报告错误(这是Carp::carp在Perl中允许执行的操作)。

编辑:请明确一点,这个问题不是关于LBYL vs EAFP,也不是关于前提条件或按契约(Contract)进行编程。如果给我这个错误的印象,我感到抱歉。这个问题是关于如何从调用堆栈的几个(一,两个)级别开始产生回溯。

EDIT2:Python的traceback模块是查找与Perl的Carp::carp等效的Python的明显地方,但是研究了一段时间之后,我找不到任何方法将其用于我想做的事情。 FWIW,Perl的Carp::carp通过公开全局变量(因此具有动态作用域)$Carp::CarpLevel,可以对追溯的初始帧进行微调。非API库函数可能会carp -out,local -ize并在输入时增加此变量(例如local $Carp::CarpLevel += 1;)。我什至看不到任何类似Python的traceback模块的东西。因此,除非我错过了任何事情,否则任何使用Python的traceback的解决方案都必须采取截然不同的方法。

最佳答案

这实际上只是一个约定问题,python中的异常处理被设计为大量使用(乞求宽恕而不是请求许可)。考虑到您在不同的语言空间中工作,您想要遵循这些约定-即/您确实确实想让开发人员知道异常的位置。但是如果您确实需要这样做...

使用检查模块

inspect module几乎可以完成重建漂亮版本的鲤 fish 所需的所有工作,而无需担心装饰器(见下文)。根据comments in this answer,可能是这种方法将在cpython以外的python上中断

# revised carp.py
import sys
import inspect

def carp( msg ):
    # grab the current call stack, and remove the stuff we don't want
    stack = inspect.stack()
    stack = stack[1:]

    caller_func = stack[0][1]
    caller_line = stack[0][2]
    sys.stderr.write('%s at %s line %d\n' % (msg, caller_func, caller_line))

    for idx, frame in enumerate(stack[1:]):
        # The frame, one up from `frame`
        upframe = stack[idx]
        upframe_record = upframe[0]
        upframe_func   = upframe[3]
        upframe_module = inspect.getmodule(upframe_record).__name__

        # The stuff we need from the current frame
        frame_file = frame[1]
        frame_line = frame[2]

        sys.stderr.write( '\t%s.%s ' % (upframe_module, upframe_func) )
        sys.stderr.write( 'called at %s line %d\n' % (frame_file, frame_line) )

    # Exit, circumventing (most) exception handling
    sys.exit(1)

对于以下示例:
  1 import carp
  2
  3 def f():
  4     carp.carp( 'carpmsg' )
  5
  6 def g():
  7     f()
  8
  9 g()

产生输出:
msg at main.py line 4
        __main__.f called at main.py line 7
        __main__.g called at main.py line 9

使用追溯

这是最初提出的方法。

也可以通过操纵回溯对象用python编写与carp等效的代码,请参阅traceback module中的文档。这样做的主要挑战是注入(inject)异常和回溯打印代码。值得注意的是,本节中的代码非常脆弱。
# carp.py
import sys
import traceback

'''
carp.py - partial emulation of the concept of perls Carp::carp
'''

class CarpError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)

def carpmain( fun ):
    def impl():
        try:
            fun()
        except CarpError as ex:
            _, _, tb = sys.exc_info()
            items = traceback.extract_tb(tb)[:-1]
            filename, lineno, funcname, line = items[-1]
            print '%s at %s line %d' % (ex.value, filename, lineno)
            for item in items[1:]:
                filename, lineno, funcname, line = item
                print '\t%s called at %s line %d' % (funcname, filename, lineno)
    return impl

def carp( value ):
    raise CarpError( value )

可以使用以下基本过程进行调用:
import carp

def g():
    carp.carp( 'pmsg' )

def f():
    g()

@carp.carpmain
def main():
    f()

main()

输出为:
msg at foo.py line 4
    main called at foo.py line 12
    f called at foo.py line 7
    g called at foo.py line 4

Perl引用示例

为了完整起见,通过将结果与等效的perl示例进行比较,调试了此答案中提出的两种解决方案:
  1 use strict;
  2 use warnings;
  3 use Carp;
  4
  5 sub f {
  6     Carp::carp("msg");
  7 }
  8
  9 sub g {
 10     f();
 11 }
 12
 13 g();

具有以下输出:
msg at foo.pl line 6
    main::f() called at foo.pl line 10
    main::g() called at foo.pl line 13

07-26 03:46