python - 从调用者的角度发出警告(又名 Python 等同于 Perl 的鲤鱼)?

标签 python stack-trace callstack

简短版:

Is there way to achieve in Python the same effect achieved by Perl's <a href="http://perldoc.perl.org/Carp.html" rel="noreferrer noopener nofollow">Carp::carp</a> utility?

长版(对于那些不熟悉 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) ) 作为回溯的最后一行。

我们需要的是某种“从调用者的角度”报告错误的方法(这就是 Carp::carp 允许人们在 Perl 中做的事情)。

编辑:需要明确的是,这个问题不是关于 LBYL 与 EAFP,也不是关于先决条件或按契约(Contract)编程。如果我给了这个错误的印象,我很抱歉。这个问题是关于如何从调用堆栈的几层(一层、两层)开始产生回溯。

EDIT2:Python 的 traceback模块显然是寻找 Perl 的 Carp::carp 的 Python 等价物的地方。 ,但在研究了一段时间后,我无法找到任何方法将它用于我想做的事情。 FWIW,Perl 的 Carp::carp允许通过公开全局(因此动态范围)变量 $Carp::CarpLevel 来微调回溯的初始框架.可能 carp 的非 API 库函数-出,local -ize 并在输入时增加此变量(例如 local $Carp::CarpLevel += 1; )。我没有看到任何甚至远程像这个 Python 的 traceback模块。所以,除非我遗漏了什么,否则任何使用 Python 的 traceback 的解决方案将不得不采取完全不同的策略......

最佳答案

这实际上只是一个约定问题,python 中的异常处理旨在大量使用(请求原谅而不是请求许可)。鉴于您在不同的语言空间中工作,您希望遵循这些约定——即/您确实想让开发人员知道异常站点在哪里。但是如果你真的需要这样做......

使用检查模块

inspect module将完成重建一个漂亮版本的 carp 所需的几乎所有工作,无需担心装饰器(见下文)。根据 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

使用回溯

这是最初提出的方法。

carp 的等价物也可以通过操作回溯对象用 python 编写,请参阅 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

关于python - 从调用者的角度发出警告(又名 Python 等同于 Perl 的鲤鱼)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8275745/

相关文章:

android - 无法加载 memtrack 模块 Logcat 错误

javascript - 避免调用堆栈错误

python - 如何使用 matplotlib 子图进行多行布局

python - 来自 scipy.special 的 fadeeva 函数的二阶导数

python - Python中模拟类构造函数默认参数

multithreading - 如何在 Visual Studio 中所有线程的调用堆栈中转储或搜索

android - 如何在 android 平台上的可执行文件中使用 CallStack(在 CallStack.tpp 中)?

python - 为什么这个 TFIDF 代码输出所有单词的频率为 0?

java - Mule - 访问异常堆栈并记录特定部分

ruby-on-rails - 永久设置rails控制台堆栈回溯限制