Нередко, получая исключительную ситуацию при работе с программой, мы не в силах сходу определить что же произошло не так как задумывалось, особенно когда отображаемая информация содержит в себе лишь немного полезного. Увидев как подается исключительная ситуация в фрейворке Django, мне захотелось так же организовать вывод и в своем коде. Оттуда был взять фрагмент кода, находящегося в модуле views/debug.py, и переделан под собственные нужды.
Итак, ниже представлен тестовый фрагмент кода, в котором произойдет ошибка.
import traceback, datetime, sys, re
def error_func(x,y):
return (x+y)/(x-y)
class A(object):
def a(self):
a1=1
a2=1
return error_func(a1, a2)
try:
a=A()
a.a()
except Exception:
# здесь наш обработчик
Если бы данный код не был облечен в инструкции обработки исключений, то интерпретатор быдал бы следующее:
File "C:\Documents and Settings\mer\workspace\test\src\a.py", line 125, in
a.a()
File "C:\Documents and Settings\mer\workspace\test\src\a.py", line 121, in a
return error_func(a1, a2)
File "C:\Documents and Settings\mer\workspace\test\src\a.py", line 115, in error_func
return (x+y)/(x-y)
ZeroDivisionError: integer division or modulo by zero
Если такая информация выглядит скудной, то исправит положение определяемая ниже функция get_tb_full()
Функция get_tb() возвращает строку с информацией об исключении как это делает по умолчанию интерпретатор (пример выше). Такая функция может пригодиться если необходимо далее каким либо образом обработать полученную строку.
tmp=traceback.format_exception(sys.exc_info()[0],
sys.exc_info()[1],
sys.exc_info()[2],)
return ''.join(tmp)
Функция get_tb_full() так же возвращает строку в которой и привидена более полная информация об исключении. Параметр message - сообщение которое будет прикрепленно к выводу, only_last - будет выведен только самый нижний уровень, тот на котором непосредственно возникла исключительная ситуация.
def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None):
"""
Returns context_lines before and after lineno from file.
Returns (pre_context_lineno, pre_context, context_line, post_context).
"""
source=None
if loader is not None and hasattr(loader, "get_source"):
source=loader.get_source(module_name)
if source is not None:
source=source.splitlines()
if source is None:
try:
f=open(filename)
try:
source=f.readlines()
finally:
f.close()
except (OSError, IOError):
pass
if source is None:
return None, [], None, []
encoding='ascii'
for line in source[:2]:
# File coding may be specified. Match pattern from PEP-263
# (http://www.python.org/dev/peps/pep-0263/)
match=re.search(r'coding[:=]\s*([-\w.]+)', line)
if match:
encoding=match.group(1)
break
source=[unicode(sline, encoding, 'replace') for sline in source]
lower_bound=max(0, lineno - context_lines)
upper_bound=lineno + context_lines
pre_context=[line.strip('\n') for line in source[lower_bound:lineno]]
context_line=source[lineno].strip('\n')
post_context=[line.strip('\n') for line in source[lineno+1:upper_bound]]
return lower_bound, pre_context, context_line, post_context
date_and_time=datetime.datetime.now()
frames=[]
tb=sys.exc_info()[-1]
# перебираем уровни которые приняли сигнал об исключительной ситуации
# заполняя список frames словарями с необходимми аттрибутами
while tb is not None:
# support for __traceback_hide__ which is used by a few libraries
# to hide internal frames.
if tb.tb_frame.f_locals.get('__traceback_hide__'):
tb=tb.tb_next
continue
filename=tb.tb_frame.f_code.co_filename
function=tb.tb_frame.f_code.co_name
lineno=tb.tb_lineno - 1
loader=tb.tb_frame.f_globals.get('__loader__')
module_name=tb.tb_frame.f_globals.get('__name__')
pre_context_lineno, pre_context, context_line, post_context=_get_lines_from_file(filename, lineno, 7, loader, module_name)
if pre_context_lineno is not None:
frames.append({
'tb': tb,
'filename': filename,
'function': function,
'lineno': lineno + 1,
'vars': tb.tb_frame.f_locals.items(),
'id': id(tb),
'pre_context': pre_context,
'context_line': context_line,
'post_context': post_context,
'pre_context_lineno': pre_context_lineno + 1,
'module':module_name
})
tb=tb.tb_next
frames_len_orig=range(len(frames)-1, -1, -1)
if only_last:
frames=list(frames[-1:])
# заносим в результирующий список строки с выводом
result=[]
result.append('='*100)
result.append('%s %s'%(date_and_time, message))
result.append('Exception raised: %s %s'%sys.exc_info()[:2])
for i,item in enumerate(frames):
result.append('-'*100)
result.append('depth level=%i. %s in module "%s"'%(frames_len_orig[i-len(frames)], item['tb'], item['module']))
result.append('filename "%s" function "%s" in string %s:'%(item['filename'], item['function'], item['lineno']))
code=item['pre_context']+[item['context_line']]+item['post_context']
for y,value in enumerate(code):
result.append('\t%i| %s'%(y+item['pre_context_lineno'],value))
result.append('local variables:')
for y in item['vars']:
result.append('\t\t%s=%s'%y)
result.append('='*100)
out=[]
for i in result:
try:
t=unicode(i)
except Exception:
t=repr(i)
out.append(t)
# преобразуем список строк в строку с разделитем переноса строки
return '\r\n'.join(out)+\r\n
Если в качестве обработчика в тестовом коде мы используем:
==================================================================================================== 2011-08-04 12:20:12.211000 This is happen:( Exception raised:integer division or modulo by zero ---------------------------------------------------------------------------------------------------- depth level=2. in module "__main__" filename "C:\Documents and Settings\mer\workspace\test\src\a.py" function " " in string 125: 118| def a(self): 119| a1=1 120| a2=1 121| return error_func(a1, a2) 122| 123| try: 124| a=A() 125| a.a() 126| except Exception: 127| print get_tb2('This is happen:(', False) 128| print 129| print get_tb2(only_last=True) 130| print 131| print get_tb() local variables: A= a=<__main__.A object at 0x00B64770> get_tb= error_func= __builtins__= __file__=C:\Documents and Settings\mer\workspace\test\src\a.py traceback= __package__=None sys= re= __name__=__main__ get_tb2= datetime= __doc__=None ---------------------------------------------------------------------------------------------------- depth level=1. in module "__main__" filename "C:\Documents and Settings\mer\workspace\test\src\a.py" function "a" in string 121: 114| def error_func(x,y): 115| return (x+y)/(x-y) 116| 117| class A(object): 118| def a(self): 119| a1=1 120| a2=1 121| return error_func(a1, a2) 122| 123| try: 124| a=A() 125| a.a() 126| except Exception: 127| print get_tb2('This is happen:(', False) local variables: a1=1 self=<__main__.A object at 0x00B64770> a2=1 ---------------------------------------------------------------------------------------------------- depth level=0. in module "__main__" filename "C:\Documents and Settings\mer\workspace\test\src\a.py" function "error_func" in string 115: 108| result.append('='*100) 109| 110| # преобразуем список строк в строку с разделитем переноса строки 111| return '\n'.join(result) 112| 113| 114| def error_func(x,y): 115| return (x+y)/(x-y) 116| 117| class A(object): 118| def a(self): 119| a1=1 120| a2=1 121| return error_func(a1, a2) local variables: y=1 x=1 ====================================================================================================
Модифицируя последнюю часть кода функции get_tb_full() можно настроить вывод по своему вкусу.
Вы представляете как это будет выглядеть со стеком в 20-30 вызовов )) А вообще действительно удобно. Иногда жалею, что не везде есть страницы ошибки, как в Django ))
ОтветитьУдалить