在我的,而不是库代码中停止异常

时间:2016-05-06 09:51:07

标签: python exception exception-handling ipython

我正在使用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 个答案:

答案 0 :(得分:8)

我会修改代码:

try:
    resp = urllib.request.urlopen(req)

except Exception as e:
    raise RuntimeError(e)

那样:

  • %pdb将您带到您的代码
  • 将原始异常保留为&#34; secondary&#34;的参数。异常。

你也可以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()调用中引发异常时:

with MonkeyPatchUrllib():
    #your code here

%pdb只会让你离你的代码只有1级。

[编辑]

使用sys.exc_info(),可以保留原始异常的更详细的上下文(如其回溯)。

答案 1 :(得分:3)

pdb只有增量帧定位(向上或向下移动帧列表)。

要获得所需功能,您可以尝试trepangithub repository)。它有一个IPython扩展here。然后,一旦出现异常,您就可以使用命令frame -1

  

框架(绝对框架定位)

     

frame [thread-Name * | * thread-number] [frame-number]

     

如果指定,则将当前帧更改为帧帧编号,如果未指定帧编号,则将当前帧更改为0。

     

如果给出了线程名称或线程号,请将当前帧更改为该线程中的帧。点(。)可用于指示调试器停止的当前帧的名称。

     

负数表示来自另一个或最近最少输入的结尾的位置。因此帧-1移动到最旧的帧,帧0移动到最新的帧。任何计算到数字的变量或表达式都可以用作位置,但是由于解析限制,位置表达式必须被视为一个空白分隔的参数。也就是说,表达式(5 * 3)-1是可以的,而(5 * 3) - 1)则不是。

进入所需的框架后,您可以使用edit修改代码。

您可能会发现命令backtrace也很有用,因为它给出了底部较少呼叫的堆栈跟踪。

trepan取决于uncompyle6可用here

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.
[...]
"""

答案 2 :(得分:2)

可以通过一些黑客来完成。 These docs显示如何在入口点使用以下代码启用事后调试:

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命令,则会发现自己处于库框架中。

答案 3 :(得分:0)

我认为答案是肯定的。

pdb在异常处停止并向您显示堆栈。

为什么隐藏异常的真正来源会有用呢?

如果它似乎正在请求并隐藏6层堆栈,您将如何解决该问题?

如果仍然不是主题,请添加到您的问题中。

答案 4 :(得分:-1)

urllib可以引发很多例外。

您需要在调用urllib周围放置一个try块,并计算如何处理异常,例如:

try:
    resp = urllib.request.urlopen(req)   

except URLError as e:
    # analyse e to figure out the detail
    ...

当然在python2的urllib下会抛出很多其他异常。 我不确定python3&urllib。