Python异常处理 - 行号

75

我正在使用Python来评估一些测量数据。由于可能的结果很多,处理或可能的组合很困难。有时在评估过程中会出现错误。通常是索引错误,因为我从测量数据中获取超出范围的值。

很难找出代码中问题发生的位置。如果我使用以下代码,将会有很大帮助:

try:
    result = evaluateData(data)
except Exception, err:
    print ("Error: %s.\n" % str(err))

很不幸,这只告诉了我发生了索引错误。我想知道更多有关异常的详细信息(代码中的行数、变量等),以便找出发生了什么。这有可能吗?

谢谢。


1
请参考以下链接:https://dev59.com/ZHA65IYBdhLWcg3wqQY8! - Charles Beattie
https://docs.python.org/2/library/traceback.html#traceback-examples - Sergey Orshanskiy
3
答案看起来不错。请接受其中一个答案。要接受它,请点击答案旁边的空白复选标记。 - Be Kind To New Users
在这里得到最高投票的答案也在@CharlesBeattie上面提到的stackoverflow问题中呈现。那个答案有重要的评论,而这个答案没有。花时间去查看那个答案绝对是值得的。 - harperville
这回答解决了你的问题吗?当我捕获一个异常时,如何获取类型、文件和行号? - Ciro Santilli OurBigBook.com
10个回答

103

解决方案,打印文件名、行号、行本身和异常描述:

import linecache
import sys

def PrintException():
    exc_type, exc_obj, tb = sys.exc_info()
    f = tb.tb_frame
    lineno = tb.tb_lineno
    filename = f.f_code.co_filename
    linecache.checkcache(filename)
    line = linecache.getline(filename, lineno, f.f_globals)
    print 'EXCEPTION IN ({}, LINE {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj)


try:
    print 1/0
except:
    PrintException()

输出:

EXCEPTION IN (D:/Projects/delme3.py, LINE 15 "print 1/0"): integer division or modulo by zero

6
对于我的Python 3.6.5,我需要在最后一行print语句中添加括号:print('EXCEPTION IN ({}, LINE {} "{}"): {}'.format(filename, lineno, line.strip(), exc_obj))。 - Chris
1
这将为您提供堆栈跟踪中第一个文件的信息。但大多数情况下,您想要最后一个,因为那里才是错误实际发生的地方。 - reubano
我们如何获取最后一个?@reubano - alper
@alper 请查看我的回答 - reubano
这并没有报告异常被引发的行,只报告了它被捕获的位置。 - NeilG

44

如果只是想获取行号,您可以使用sys。如果需要更多信息,请尝试traceback模块。

import sys    
try:
    [][2]
except IndexError:
    print("Error on line {}".format(sys.exc_info()[-1].tb_lineno))

打印:

Error on line 3

来自traceback模块文档的示例:

import sys, traceback

def lumberjack():
    bright_side_of_death()

def bright_side_of_death():
    return tuple()[0]

try:
    lumberjack()
except IndexError:
    exc_type, exc_value, exc_traceback = sys.exc_info()
    print "*** print_tb:"
    traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
    print "*** print_exception:"
    traceback.print_exception(exc_type, exc_value, exc_traceback,
                              limit=2, file=sys.stdout)
    print "*** print_exc:"
    traceback.print_exc()
    print "*** format_exc, first and last line:"
    formatted_lines = traceback.format_exc().splitlines()
    print formatted_lines[0]
    print formatted_lines[-1]
    print "*** format_exception:"
    print repr(traceback.format_exception(exc_type, exc_value,
                                          exc_traceback))
    print "*** extract_tb:"
    print repr(traceback.extract_tb(exc_traceback))
    print "*** format_tb:"
    print repr(traceback.format_tb(exc_traceback))
    print "*** tb_lineno:", exc_traceback.tb_lineno

29

我使用简单而健壮的traceback

import traceback

try:
    raise ValueError()
except:
    print(traceback.format_exc())  # or: traceback.print_exc()

输出:

Traceback (most recent call last):
  File "catch.py", line 4, in <module>
    raise ValueError()
ValueError

1
这是最佳答案。当错误在函数内部时,其他答案无法正确显示错误行。 - alynurly

16

最简单的方法就是直接使用:

import traceback
try:
    <blah>
except IndexError:
    traceback.print_exc()

或者如果使用日志记录:

import logging
try:
    <blah>
except IndexError as e:
    logging.exception(e)

5
提供了调用堆栈中最后一项的文件、行号和异常信息。
from sys import exc_info
from traceback import format_exception


def print_exception():
    etype, value, tb = exc_info()
    info, error = format_exception(etype, value, tb)[-2:]
    print(f'Exception in:\n{info}\n{error}')

try:
    1 / 0
except:
    print_exception()

打印

Exception in:
   File "file.py", line 12, in <module>
    1 / 0

ZeroDivisionError: division by zero


1
这里已经有很多回答展示了如何获取行号,但值得注意的是,如果你想要包含“原始数据”的变量,这样你就可以更细粒度地控制你要显示或者格式化的内容,那么使用traceback模块,你可以逐帧遍历堆栈并查看存储在帧摘要对象属性中的内容。有几种简单而优雅的方法可以直接操作帧摘要对象。举个例子,假设你想要获取堆栈中最后一帧的行号(它告诉你哪行代码触发了异常),你可以通过访问相关的帧摘要对象来获取它。

选项1:

import sys
import traceback
try:
    # code that raises an exception
except Exception as exc:
    exc_type, exc_value, exc_tb = sys.exc_info()
    stack_summary = traceback.extract_tb(exc_tb)
    end = stack_summary[-1]  # or `stack_summary.pop(-1)` if you prefer

Option 2:

import sys
import traceback
try:
    # code that raises an exception
except Exception as exc:
    tbe = traceback.TracebackException(*sys.exc_info())
    end = tbe.stack[-1]  # or `tbe.stack.pop(-1)` if you prefer

在上述示例中,end将是一个框架摘要对象:
>>> type(end)
&ltclass 'traceback.FrameSummary'>
这个对象又来自于一个堆栈摘要对象:
>>> type(stack_summary)  # from option 1
&ltclass 'traceback.StackSummary'>
>>> type(tbe.stack)  # from option 2
&ltclass 'traceback.StackSummary'>
堆栈摘要对象的行为类似于列表,您可以按照任何您想要的方式迭代其中的所有框架摘要对象,以便跟踪错误。框架摘要对象(例如本例中的end)包含了行号和其他定位异常发生位置所需的信息:
>>> print(end.__doc__)
A single frame from a traceback.
- :attr:`filename` The filename for the frame. - :attr:`lineno` The line within filename for the frame that was active when the frame was captured. - :attr:`name` The name of the function or method that was executing when the frame was captured. - :attr:`line` The text from the linecache module for the of code that was running when the frame was captured. - :attr:`locals` Either None if locals were not supplied, or a dict mapping the name to the repr() of the variable.
如果捕获异常对象(无论是使用except Exception as exc:语法还是从sys.exc_info()返回的第二个对象),则您将拥有编写自己高度定制的错误打印/记录函数所需的所有内容。
err_type = type(exc).__name__
err_msg = str(exc)

把所有东西放在一起:

将其整合:

from datetime import datetime
import sys
import traceback


def print_custom_error_message():
    exc_type, exc_value, exc_tb = sys.exc_info()
    stack_summary = traceback.extract_tb(exc_tb)
    end = stack_summary[-1]

    err_type = type(exc_value).__name__
    err_msg = str(exc_value)
    date = datetime.strftime(datetime.now(), "%B %d, %Y at precisely %I:%M %p")

    print(f"On {date}, a {err_type} occured in {end.filename} inside {end.name} on line {end.lineno} with the error message: {err_msg}.")
    print(f"The following line of code is responsible: {end.line!r}")
    print("Please make a note of it.")


def do_something_wrong():
    try:
        1/0
    except Exception as exc:
        print_custom_error_message()


if __name__ == "__main__":
    do_something_wrong()

让我们运行它!

user@some_machine:~$ python example.py
在2022年8月25日凌晨01:31,example.py文件中do_something_wrong函数的第21行发生了ZeroDivisionError错误,错误信息为:division by zero。
导致错误的代码行是:'1/0'
请记下来。

此时,你可以看到如何将此消息打印到堆栈的任何位置:结尾、开头、中间任何位置,或者遍历并打印每个堆栈帧。

当然,traceback模块提供的格式化功能已经涵盖了大多数调试用例,但了解如何操作traceback对象以提取所需信息非常有用。


1
我建议使用Python日志库,它有两个有用的方法可以在这种情况下提供帮助。
1. logging.findCaller() - findCaller(stack_info=False) - 仅报告导致异常抛出的前一个调用者的行号 - findCaller(stack_info=True) - 报告导致异常抛出的前一个调用者的行号和堆栈
2. logging.logException() - 报告引发异常的try/except块内的行号和堆栈
欲了解更多信息,请查看API https://docs.python.org/3/library/logging.html

0

所有的解决方案都回答了OP的问题,但是如果一个人持有特定的错误实例并且最后的回溯堆栈无法解决它们,那么它们将不足够——下面给出了一个角落场景的例子。

在这种情况下,可以使用引发异常实例的魔术属性__traceback__这是一个系统回溯对象(公开为types.TracebackType,请参见Types),而不是模块。 这个回溯实例的行为就像预期的一样(但检查一下总是很好):

from collections import deque
from typing import (List, )
errors: List[Exception] = deque([], 5)
    
def get_raised(error_cls: type, msg: str) -> Exception:
# for debugging, an exception instance that is not raised
# ``__traceback__`` has ``None``
    try:
        raise error_cls(msg)
    except Exception as error:
        return error

error = get_raised(NameError, 'foo')
errors.append(error)

error = get_raised(ValueError, 'bar')
errors.append(error)

try:
    raise get_raised(TypeError, 'bar')
except Exception as error:
    errors.append(error)

现在,我可以查看第一个错误的详细信息:

import types

traceback = errors[0].__traceback__  # inadvisable name due to module
line_no: int = traceback.tb_lineno
frame: types.FrameType = traceback.tb_frame
previous: Union[type(None), types.TracebackType] = traceback.tb_next
filename: str = frame.f_code.co_filename

回溯的第二个错误,尽管有一个预期的前置错误,但其previous为None,而对于第三个错误,在其中引发错误两次时不是如此。

以下仅是一个测试,无意义。当Web应用程序的视图(500状态类似事件)引发异常并被捕获并存储以供管理员检查时,这将非常有用,类似于Setry.io(但免费)。这是一个最小的示例,其中主页/会引发一个错误,该错误会被捕获并在路由errors中列出。这是使用Pyramid非常集中的方式(多文件更好),没有日志记录或身份验证,并且错误日志记录可能更好,供管理员检查类似于Sentry.io。

from pyramid.config import Configurator
from waitress import serve
from collections import deque
# just for typehinting:
from pyramid.request import Request
from pyramid.traversal import DefaultRootFactory
from pyramid.router import Router
import types
from typing import (List, )


def home_view(context: DefaultRootFactory, request: Request) -> dict:
    raise NotImplementedError('I forgot to fill this')
    return {'status': 'ok'}  # never reached.

def caught_view(error: Exception, request: Request) -> dict:
    """
    Exception above is just type hinting.
    This is controlled by the context argument in 
    either the ``add_exception_view`` method of config,
    or the ``exception_view_config`` decorator factory (callable class)
    """
    # this below is a simplification as URLDecodeError is an attack (418)
    request.response.status = 500 
    config.registry.settings['error_buffer'].append(error)
    #logging.exception(error) # were it set up.
    #slack_admin(format_error(error))  # ditto
    return {'status': 'error',  'message': 'The server crashed!!'}

def format_error(error: Exception) -> str:
    traceback = error.__traceback__  # inadvisable name due to module
    frame: types.FrameType = traceback.tb_frame
    return f'{type(error).__name__}: {error}' +\
           f'at line {traceback.tb_lineno} in file {frame.f_code.co_filename}'

def error_view(context: DefaultRootFactory, request: Request) -> dict:
    print(request.registry.settings['error_buffer'])
    return {'status': 'ok', 
            'errors':list(map(format_error, request.registry.settings['error_buffer']))
           }
    
with Configurator(settings=dict()) as config:
    config.add_route('home', '/')
    config.add_route('errors', '/errors')
    config.add_view(home_view, route_name='home', renderer='json')
    config.add_view(error_view, route_name='errors', renderer='json')
    config.add_exception_view(caught_view, context=Exception, renderer='json')
    config.registry.settings['error_buffer']: List[Exception] = deque([], 5)  
    # not in config.registry.settings, not JSON serialisable
    # config.add_request_method
    app  : Router = config.make_wsgi_app()

port = 6969
serve(app, port=port)

0
我总是使用这个片段。
import sys, os

try:
    raise NotImplementedError("No error")
except Exception as e:
    exc_type, exc_obj, exc_tb = sys.exc_info()
    fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
    print(exc_type, fname, exc_tb.tb_lineno)

对于不同的视图和可能出现的问题,您可以参考当我捕获异常时,如何获取类型、文件和行号?


0
我看了所有其他的答案,发现它们只是给出了错误的外部线索,一点用处都没有。我猜你需要知道在你的代码中引发错误的第一行代码,这是我的解决方案:
import sys

def inner_func():
    i, j = 9, 0
    return  i / j


def outer_func():
    inner_func()


def main(args):
    outer_func()
    return 0


if __name__ == '__main__':
    try:
        sys.exit(main(sys.argv))
    except Exception as exp:
        import traceback
        _, _, tb = sys.exc_info()
        lineno = traceback.extract_tb(tb)[-1].lineno  # the first inner line of error
        red, bold_red, normal = '\033[1;31m', '\033[0;31m', '\033[0m'
        err_msg = f'{red}line[{lineno}]: {bold_red}{exp}{normal}'
        print(err_msg, file=sys.stderr, sep='')

输出结果为:

line[5]: division by zero

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接