简体   繁体   English

Python 异常处理-行号

[英]Python exception handling - line number

I'm using python to evaluate some measured data.我正在使用 python 来评估一些测量数据。 Because of many possible results it is difficult to handle or possible combinations.由于许多可能的结果,很难处理或可能的组合。 Sometimes an error happens during the evaluation.有时在评估过程中会发生错误。 It is usually an index error because I get out of range from measured data.这通常是索引错误,因为我超出了测量数据的范围。

It is very difficult to find out on which place in code the problem happened.很难找出问题发生在代码中的哪个位置。 It would help a lot if I knew on which line the error was raised.如果我知道在哪一行引发了错误,那将有很大帮助。 If I use following code:如果我使用以下代码:

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

Unfortunately this only tells me that there is and index error.不幸的是,这只告诉我存在索引错误。 I would like to know more details about the exception (line in code, variable etc.) to find out what happened.我想了解有关异常的更多详细信息(代码行、变量等)以了解发生了什么。 Is it possible?可能吗?

Thank you.谢谢你。

Solution, printing filename, linenumber, line itself and exception description:解决方案,打印文件名、行号、行本身和异常描述:

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

Output:输出:

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

To simply get the line number you can use sys , if you would like to have more, try the traceback module.要简单地获取行号,您可以使用sys ,如果您想要更多,请尝试使用traceback模块。

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

prints :打印

Error on line 3

Example from the traceback module documentation : 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

I use the traceback which is simple and robust:我使用简单而健壮的traceback

import traceback

try:
    raise ValueError()
except:
    print(traceback.format_exc())

Out:出去:

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

The simplest way is just to use:最简单的方法就是使用:

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

or if using logging:或者如果使用日志记录:

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

Gives you file, lineno, and exception for the last item in the call stack为您提供调用堆栈中最后一项的文件、行号和异常

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

prints印刷

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

ZeroDivisionError: division by zero

I would suggest using the python logging library, it has two useful methods that might help in this case.我建议使用 python 日志库,它有两个有用的方法在这种情况下可能会有所帮助。

  1. logging.findCaller() logging.findCaller()

    • findCaller(stack_info=False) - Reports just the line number for the previous caller leading to the exception raised findCaller(stack_info=False) - 只报告导致异常的前一个调用者的行号
    • findCaller(stack_info=True) - Reports the line number & stack for the previous caller leading to the exception raised findCaller(stack_info=True) - 报告前一个调用者的行号和堆栈,导致引发异常
  2. logging.logException() logging.logException()

    • Reports the line & stack within the try/except block that raised the exception报告引发异常的 try/except 块中的行和堆栈

For more info checkout the api https://docs.python.org/3/library/logging.html有关更多信息,请查看 api https://docs.python.org/3/library/logging.html

I always use this snippet我总是使用这个片段

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)

for different views and possible issues you can refer When I catch an exception, how do I get the type, file, and line number?对于不同的视图和可能的问题,您可以参考当我捕获异常时,如何获取类型、文件和行号?

All the solutions answer the OPs problem, however, if one is holding onto a specific error instance and the last traceback stack won't do it they will not suffice —a corner scenario example given below.所有的解决方案都解决了 OPs 问题,但是,如果一个人持有一个特定的错误实例并且最后一个回溯堆栈不会这样做,那么它们就不够了——下面给出了一个角落场景示例。

In this case, the magic attribute __traceback__ of a raised exception instance may be used.在这种情况下,可以使用引发异常实例的魔术属性__traceback__ This is a system traceback object (exposed as types.TracebackType , see Types ) not the module .这是一个系统回溯对象(公开为types.TracebackType ,请参阅Types )而不是 module This traceback instance behaves just one like would expect (but it is always nice to check):这个回溯实例的行为与预期的一样(但检查总是很好):

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)

Now, I can check out the first error's details:现在,我可以查看第一个错误的详细信息:

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

The traceback's previous is None for the second error despite a preceding error as expected, but for the third wherein an error is raised twice it is not.回溯的前一个错误对于第二个错误是无,尽管前面的错误如预期的那样,但对于第三个错误,其中两次引发错误,它不是。

This below is just a test which makes no sense contextually.下面只是一个测试,在上下文中没有任何意义。 A case were it is useful if when an exception is raised in a view of a webapp (a 500 status kind of incident) that gets caught and stored for the admit to inspect akin to Setry.io (but for free).如果在 webapp 的视图中引发异常(一种 500 状态的事件),该异常被捕获并存储以供承认检查类似于 Setry.io(但免费),那么这种情况很有用。 Here is a minimal example where the home page / will raise an error, that gets caught and the route errors will list them.这是一个最小的示例,主页/将引发错误,该错误被捕获并且路由errors将列出它们。 This is using Pyramid in a very concentrated way (multifile is way better) with no logging or authentication and the error logging could be better for the admin to inspect similar to Sentry.io.这是以非常集中的方式使用 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)

There are many answers already posted here that show how to get the line number, but it's worth noting that if you want variables containing the "raw data," so to speak, of the stack trace so that you can have more granular control of what you display or how you format it, using the traceback module you can step through the stack frame by frame and look at what's stored in the attributes of the frame summary objects.这里已经发布了许多答案来显示如何获取行号,但值得注意的是,如果您想要包含堆栈跟踪的“原始数据”的变量,以便您可以更精细地控制什么您显示或如何格式化它,使用traceback模块,您可以逐帧遍历堆栈并查看存储在帧摘要对象的属性中的内容。 There are several simple and elegant ways to manipulate the frame summary objects directly.有几种简单而优雅的方法可以直接操作框架摘要对象。 Let's say for example that you want the line number from the last frame in the stack (which tells you which line of code triggered the exception), here's how you could get it by accessing the relevant frame summary object:例如,假设您想要堆栈中最后一帧的行号(它告诉您哪一行代码触发了异常),您可以通过访问相关的帧摘要 object 来获得它:

Option 1:选项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:选项 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

In either of the above examples, end will be a frame summary object:在上述任一示例中, end将是帧摘要 object:

>>> type(end)
&ltclass 'traceback.FrameSummary'>

which was in turn taken from a stack summary object:这又取自堆栈摘要 object:

>>> type(stack_summary)  # from option 1
&ltclass 'traceback.StackSummary'>
>>> type(tbe.stack)  # from option 2
&ltclass 'traceback.StackSummary'>

The stack summary object behaves like a list and you can iterate through all of the frame summary objects in it however you want in order to trace through the error.堆栈摘要 object 的行为类似于列表,您可以根据需要遍历其中的所有帧摘要对象以跟踪错误。 The frame summary object ( end , in this example), contains the line number and everything else you need to locate where in the code the exception occurred:帧摘要 object(在本例中为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.

And if you capture the exception object (either from except Exception as exc: syntax or from the second object returned by sys.exc_info() ), you will then have everything you need to write your own highly customized error printing/logging function:如果您捕获异常 object(来自except Exception as exc:语法或来自sys.exc_info()返回的第二个 object ),您将拥有编写自己的高度自定义的错误打印/记录 ZC1C425268E68A45 所需的一切

err_type = type(exc).__name__
err_msg = str(exc)

Putting it all together:把它们放在一起:

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(f"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()

Let's run it!让我们运行它!

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.

At this point you can see how you could print this message for any place in the stack: end, beginning, anywhere in-between, or iterate through and print it for every frame in the stack.此时,您可以看到如何为堆栈中的任何位置打印此消息:结束、开始、中间的任何位置,或者迭代并为堆栈中的每一帧打印它。

Of course, the formatting functionality already provided by the traceback module covers most debugging use cases, but it's useful to know how to manipulate the traceback objects to extract the information you want.当然, traceback模块已经提供的格式化功能涵盖了大多数调试用例,但是了解如何操作 traceback 对象以提取所需的信息很有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM