Остановитесь на исключении в моем, а не библиотечном коде
Я разрабатываю приложение, используя библиотеку Python urllib, и иногда возникают исключения из-за невозможности получить доступ к URL-адресу.
Однако исключение поднято почти на 6 уровней в стандартный стек библиотеки:
/home/user/Workspace/application/main.py in call(path)
11 headers={'content-type': 'application/json'},
12 data=b'')
---> 13 resp = urllib.request.urlopen(req) ####### THIS IS MY CODE
14 return json.loads(resp.read().decode('utf-8'))
/usr/lib/python3.4/urllib/request.py in urlopen(url, data, timeout, cafile, capath, cadefault, context)
159 else:
160 opener = _opener
--> 161 return opener.open(url, data, timeout)
162
163 def install_opener(opener):
/usr/lib/python3.4/urllib/request.py in open(self, fullurl, data, timeout)
461 req = meth(req)
462
--> 463 response = self._open(req, data)
464
465 # post-process response
/usr/lib/python3.4/urllib/request.py in _open(self, req, data)
479 protocol = req.type
480 result = self._call_chain(self.handle_open, protocol, protocol +
--> 481 '_open', req)
482 if result:
483 return result
/usr/lib/python3.4/urllib/request.py in _call_chain(self, chain, kind, meth_name, *args)
439 for handler in handlers:
440 func = getattr(handler, meth_name)
--> 441 result = func(*args)
442 if result is not None:
443 return result
/usr/lib/python3.4/urllib/request.py in http_open(self, req)
1208
1209 def http_open(self, req):
-> 1210 return self.do_open(http.client.HTTPConnection, req)
1211
1212 http_request = AbstractHTTPHandler.do_request_
/usr/lib/python3.4/urllib/request.py in do_open(self, http_class, req, **http_conn_args)
1182 h.request(req.get_method(), req.selector, req.data, headers)
1183 except OSError as err: # timeout error
-> 1184 raise URLError(err)
1185 r = h.getresponse()
1186 except:
URLError: <urlopen error [Errno 111] Connection refused>
Обычно я запускаю код в ipython3 с включенной магией %pdb, поэтому в случае исключения я могу проверить его немедленно. Однако для этого мне нужно спуститься на 6 уровней стека, чтобы добраться до моего кода.
Возможно ли, чтобы мое приложение вылетело указывая на мой код напрямую?
5 ответов:
Я бы пошел с изменением кода:
try: resp = urllib.request.urlopen(req) except Exception as e: raise RuntimeError(e)Таким образом:
- %pdb перемещает вас в ваш код,
Исходное исключение сохраняется в качестве аргумента "вторичного" исключения.Вы также можете использовать функцию monkeypatch
urllib.request.urlopen():class MonkeyPatchUrllib(object): def __enter__(self): self.__urlopen = urllib.request.urlopen urllib.request.urlopen = self def __exit__(self, exception_type, exception_value, traceback): urllib.request.urlopen = self.__urlopen def __call__(self, *args, **kwargs): try: return self.__urlopen(*args, **kwargs) except Exception as e: raise RuntimeError(e)Всякий раз, когда у вас возникает исключение в вызове
urlibopen()в области context manager:with MonkeyPatchUrllib(): #your code here%pdb переместит вас всего на 1 Уровень от вашего кода.
[EDIT]
С
sys.exc_info()можно для сохранения более подробного контекста исходного исключения (например, его обратной трассировки).
pdbимеет только инкрементное позиционирование кадров (перемещение вверх или вниз по списку кадров).Чтобы получить нужную функцию, вы можете попробовать
trepan(репозиторий github ). Он имеет расширение IPython здесь . Затем вы используете командуframe -1, Как только появляется исключение:Как только вы попадете в нужный кадр, вы можете использоватьКадр (абсолютное позиционирование кадра)
Кадр [имя потока* / * номер потока] [номер кадра]
Измените текущий кадр на номер кадра, если указанный или текущий кадр, 0, если номер кадра не указан.
Если задано имя или номер потока, измените текущий кадр на кадр в этом потоке. Точка (.) может использоваться для указания имени текущего кадра, в котором остановлен отладчик.
Отрицательное число указывает на позицию с другого или наименее недавно введенного конца. Таким образом, кадр -1 перемещается в самый старый кадр, а кадр 0 перемещается в самый новый кадр. Любая переменная или выражение, которое вычисляется как число может использоваться в качестве позиции, однако из-за ограничений синтаксического анализа выражение позиции должно рассматриваться как один параметр с пустыми разделителями. То есть выражение (5 * 3) -1 нормально, в то время как (5 * 3) - 1) нет.
editдля изменения вашего кода.Вы можете найти команду
backtraceполезной, так как она дает трассировку стека с менее недавним вызовом внизу.
trepanзависит от наличияuncompyle6здесь .В противном случае, вы можете решить быть терпеливым и ждать улучшений. В IPython/core/debugger.py:
pydbпредоставляет аналогичную функцию, но, к сожалению, не был портирован на Python3.""" Pdb debugger class. Modified from the standard pdb.Pdb class to avoid including readline, so that the command line completion of other programs which include this isn't damaged. In the future, this class will be expanded with improvements over the standard pdb. [...] """
Это можно сделать с помощью некоторого взлома. Эти документы показывают, как можно включить посмертную отладку со следующим кодом в точке входа:
import sys from IPython.core import ultratb sys.excepthook = ultratb.FormattedTB(mode='Verbose', color_scheme='Linux', call_pdb=1)Прохождение через этот крюк после возникновения исключения показывает, что нам нужно поработать с методом
debugger. К сожалению, я не вижу лучшего способа сделать это, кроме как скопировать весь метод и изменить его там, где это необходимо (я пытался изменитьself.tb, но объекты traceback доступны только для чтения и не могут быть использованы сcopy.deepcopy). Вот это да. демо:import json import sys from IPython.core import debugger, ultratb from IPython.core.display_trap import DisplayTrap class CustomTB(ultratb.FormattedTB): def debugger(self, force=False): if force or self.call_pdb: if self.pdb is None: self.pdb = debugger.Pdb( self.color_scheme_table.active_scheme_name) # the system displayhook may have changed, restore the original # for pdb display_trap = DisplayTrap(hook=sys.__displayhook__) with display_trap: self.pdb.reset() # Find the right frame so we don't pop up inside ipython itself if hasattr(self, 'tb') and self.tb is not None: etb = self.tb else: etb = self.tb = sys.last_traceback # only modification is here -----+ # | # V while self.tb is not None and '/lib/python3' not in self.tb.tb_next.tb_frame.f_code.co_filename: self.tb = self.tb.tb_next if etb and etb.tb_next: etb = etb.tb_next self.pdb.botframe = etb.tb_frame self.pdb.interaction(self.tb.tb_frame, self.tb) if hasattr(self, 'tb'): del self.tb sys.excepthook = CustomTB(mode='Verbose', color_scheme='Linux', call_pdb=1) def foo(): bar() def bar(): json.dumps(json) foo()Как вы можете видеть, он прекращает поиск по обратной трассировке, когда он собирается достичь кода библиотеки. Вот результат:
TypeErrorTraceback (most recent call last) /Users/alexhall/Dropbox/python/sandbox3/sandbox.py in <module>() 40 json.dumps(json) 41 ---> 42 foo() global foo = <function foo at 0x1031358c8> /Users/alexhall/Dropbox/python/sandbox3/sandbox.py in foo() 35 36 def foo(): ---> 37 bar() global bar = <function bar at 0x103135950> 38 39 def bar(): /Users/alexhall/Dropbox/python/sandbox3/sandbox.py in bar() 38 39 def bar(): ---> 40 json.dumps(json) global json.dumps = <function dumps at 0x10168b268> global json = <module 'json' from '/Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py'> 41 42 foo() /Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py in dumps(obj=<module 'json' from '/Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py'>, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw={}) 228 cls is None and indent is None and separators is None and 229 default is None and not sort_keys and not kw): --> 230 return _default_encoder.encode(obj) global _default_encoder.encode = <bound method JSONEncoder.encode of <json.encoder.JSONEncoder object at 0x10166e8d0>> obj = <module 'json' from '/Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py'> 231 if cls is None: 232 cls = JSONEncoder /Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/encoder.py in encode(self=<json.encoder.JSONEncoder object>, o=<module 'json' from '/Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py'>) 197 # exceptions aren't as detailed. The list call should be roughly 198 # equivalent to the PySequence_Fast that ''.join() would do. --> 199 chunks = self.iterencode(o, _one_shot=True) chunks = undefined self.iterencode = <bound method JSONEncoder.iterencode of <json.encoder.JSONEncoder object at 0x10166e8d0>> o = <module 'json' from '/Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py'> global _one_shot = undefined 200 if not isinstance(chunks, (list, tuple)): 201 chunks = list(chunks) /Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/encoder.py in iterencode(self=<json.encoder.JSONEncoder object>, o=<module 'json' from '/Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py'>, _one_shot=True) 255 self.key_separator, self.item_separator, self.sort_keys, 256 self.skipkeys, _one_shot) --> 257 return _iterencode(o, 0) _iterencode = <_json.Encoder object at 0x1031296d8> o = <module 'json' from '/Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py'> 258 259 def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, /Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/encoder.py in default(self=<json.encoder.JSONEncoder object>, o=<module 'json' from '/Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py'>) 178 179 "" --> 180 raise TypeError(repr(o) + " is not JSON serializable") global TypeError = undefined global repr = undefined o = <module 'json' from '/Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py'> 181 182 def encode(self, o): TypeError: <module 'json' from '/Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py'> is not JSON serializable > /Users/alexhall/Dropbox/python/sandbox3/sandbox.py(40)bar() 38 39 def bar(): ---> 40 json.dumps(json) 41 42 foo() ipdb> down > /Users/alexhall/.pyenv/versions/3.5.0/lib/python3.5/json/__init__.py(230)dumps() 228 cls is None and indent is None and separators is None and 229 default is None and not sort_keys and not kw): --> 230 return _default_encoder.encode(obj) 231 if cls is None: 232 cls = JSONEncoder ipdb>В основном полная обратная трассировка все еще распечатывается, но
ipdbначинается с вашего собственного кода. Если вы введете командуdown, то окажетесь в фрейме библиотеки.
Я думаю, что ответ-нет.
Pdb останавливается на исключении и показывает вам стек.
Почему было бы полезно скрыть реальный источник исключения?
Если бы он работал так, как вы, кажется, просите, и скрывает 6 слоев стека, как бы вы решили, что исправить?
Если это все еще не по теме, пожалуйста, добавьте к вашему вопросу.
Urllib может вызывать множество исключений.
Вам нужно поместить блок try вокруг вызова в urllib и выяснить, как обрабатывать исключения, например:
try: resp = urllib.request.urlopen(req) except URLError as e: # analyse e to figure out the detail ...Конечно, под urllib python2 выбрасывается много других исключений. Я не уверен насчет urllib python3.
Comments