Остановитесь на исключении в моем, а не библиотечном коде



Я разрабатываю приложение, используя библиотеку 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 уровней стека, чтобы добраться до моего кода.



Возможно ли, чтобы мое приложение вылетело указывая на мой код напрямую?

531   5  

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 здесь .

pydb предоставляет аналогичную функцию, но, к сожалению, не был портирован на Python3.

В противном случае, вы можете решить быть терпеливым и ждать улучшений. В IPython/core/debugger.py:
"""
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

    Ничего не найдено.