Python 异常处理-行号

我正在使用 python 来评估一些测量数据。由于许多可能的结果,很难处理或可能的组合。有时在评估过程中会发生错误。这通常是一个指数误差,因为我得到的范围从测量数据。

很难找出问题发生在代码中的哪个位置。如果我知道错误出现在哪一行,将会有很大帮助。如果我使用以下代码:

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

不幸的是,这只告诉我有和索引错误。我想知道更多关于异常的细节(代码行,变量等) ,以找出发生了什么。有可能吗?

谢谢你。

96274 次浏览

要简单地获得行号,您可以使用 sys,如果您希望获得更多,请尝试使用 追踪模块。

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

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

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

最简单的方法就是:

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

或如果使用日志记录:

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

我使用的是简单而稳健的 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

我建议使用 python 日志库,它有两个有用的方法,在这种情况下可能会有所帮助。

  1. FindCaller ()

    • FindCaller (stack _ info = False)-仅报告导致引发异常的前一个调用方的行号
    • FindCaller (stack _ info = True)-报告导致引发异常的前一个调用方的行号和堆栈
  2. LogException ()

    • 报告引发异常的 try/other 块中的 line & stack

更多信息请查看 api https://docs.python.org/3/library/logging.html

我总是用这个片段

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)

对于不同的观点和可能的问题,你可以参考 当我捕捉到异常时,如何获取类型、文件和行号?

为调用堆栈中的最后一项提供文件、 lineno 和异常

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


然而,所有的解决方案都回答了 OP 问题,如果一个解决方案保留了一个特定的错误实例,而最后一个回溯堆栈不能完成这个任务,那么这些解决方案就是不够的ーー下面给出的一个角落场景示例。

在这种情况下,可以使用引发的异常实例的 magic 属性 __traceback__这是一个系统回溯对象(公开为 types.TracebackType,参见 < a href = “ https://docs.python.org/3/library/typees.html”rel = “ nofollow noReferrer”> Type ) ,而不是模块.这个跟踪实例的行为与预期的一样(但是检查起来总是很好) :

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

对于第二个错误,回溯的前一个错误是“无”,尽管如预期的那样出现了前一个错误,但是对于第三个错误,其中一个错误被引发了两次,则不是“无”。

下面这个测试在上下文中毫无意义。一种情况是,如果在一个 webapp (一种500状态类型的事件)视图中引发了一个异常,该异常被捕获并存储以供类似于 Setry.io (但是免费)的承认检查,这种情况是有用的。下面是一个最小的例子,其中主页 /将引发一个错误,该错误将被捕获,路由 errors将列出它们。这是以一种非常集中的方式使用金字塔(多文件更好) ,没有日志记录或身份验证,而且错误日志记录对于管理员来说可以更好地进行类似于 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)

这里已经有很多答案展示了如何获得行号,但是值得注意的是,如果你想要包含“原始数据”的变量,也就是说,堆栈跟踪,这样你就可以有更多的粒度控制你显示什么或如何格式化它,使用 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

选择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)
<class 'traceback.FrameSummary'>

它又是从堆栈摘要对象中获取的:

>>> type(stack_summary)  # from option 1
<class 'traceback.StackSummary'>
>>> type(tbe.stack)  # from option 2
<class '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
On August 25, 2022 at precisely 01:31 AM, a ZeroDivisionError occured in example.py inside do_something_wrong on line 21 with the error message: division by zero.
The following line of code is responsible: '1/0'
Please make a note of it.

此时,您可以看到如何为堆栈中的任何位置打印此消息: 结束、开始、中间的任何位置,或迭代并为堆栈中的每个帧打印此消息。

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