2016-05-06 17 views
9

Sto sviluppando un'applicazione utilizzando una libreria Python urllib e talvolta si verificano eccezioni a causa dell'impossibilità di accedere a un URL.Arresto ad eccezione nel mio codice libreria, non

Tuttavia, l'eccezione viene sollevata quasi 6 livelli nella pila libreria standard:

/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> 

Io di solito eseguire il codice in ipython3 con la magia %pdb acceso così nel caso in cui v'è un'eccezione che posso ispezionarlo subito. Comunque per questo devo scendere i 6 livelli dello stack per arrivare al mio codice.

È possibile che la mia app si arresti in modo anomalo puntando direttamente al mio codice?

risposta

8

vorrei andare con la modifica del codice:

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

except Exception as e: 
    raise RuntimeError(e) 

questo modo:

  • % mosse PDB al vostro codice,
  • l'eccezione originale è conservato come argomento del "secondario " eccezione.

Si può anche monkeypatch urllib.request.urlopen() funzione:

Ogni volta che si dispone di un'eccezione sollevata in urlibopen() chiamata nell'ambito di applicazione contesto manager:

with MonkeyPatchUrllib(): 
    #your code here 

% PDB si muoverà solamente 1 livello lontano dal tuo codice.

[EDIT]

Con sys.exc_info() è possibile conservare un contesto più dettagliato del eccezione originale (come il suo traceback).

-1

urllib può sollevare molte eccezioni.

è necessario mettere un blocco try intorno la chiamata in urllib e capire come gestire le eccezioni, ad esempio:

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

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

certamente sotto un sacco di urllib python2 di altre eccezioni sono gettati. Non sono sicuro dell'urllib di python3.

0

Penso che la risposta sia no.

pdb si ferma all'eccezione e mostra lo stack.

Perché sarebbe utile nascondere la vera fonte dell'eccezione?

Se ha funzionato come sembra richiedere e nasconde i 6 strati di stack, come risolverebbe cosa risolvere?

Se questo non è ancora in argomento si prega di aggiungere alla tua domanda.

+0

'Perché sarebbe utile nascondere la vera fonte dell'eccezione?'Perché è il codice della libreria. Mi aspetto che la libreria propaghi l'eccezione fino a dove la chiamo nel mio codice. – TheMeaningfulEngineer

3

pdb ha solo il posizionamento incrementale del frame (spostandosi verso l'alto o verso il basso nell'elenco dei frame).

Per ottenere la funzionalità desiderata, è possibile provare trepan (github repository). Ha un'estensione IPython here. Quindi si utilizza il comando frame -1 volta eccezione presenta:

Frame (posizionamento riferimento assoluto)

telaio [filo-Nome * | * filo-numero] [fotogramma numero]

Cambia il frame corrente in frame-number se specificato, o il frame corrente, 0, se nessun numero di frame è specificato.

Se viene specificato il nome di un thread o un numero di thread, modificare il frame corrente in un frame in tale thread. Dot (.) Può essere utilizzato per indicare il nome del frame corrente in cui il debugger è interrotto.

Un numero negativo indica la posizione dall'altra o dalla fine inserita meno recente. Quindi il fotogramma -1 si sposta sul fotogramma più vecchio e il fotogramma 0 si sposta sul fotogramma più recente. Qualsiasi variabile o espressione che valuti un numero può essere utilizzata come posizione, tuttavia a causa delle limitazioni di analisi, l'espressione di posizione deve essere vista come un singolo parametro delimitato da vuoto. Cioè, l'espressione (5 * 3) -1 va bene mentre (5 * 3) - 1) non lo è.

Una volta entrato nella cornice desiderata, è possibile utilizzare edit per modificare il codice.

È possibile che il comando backtrace sia utile anche perché fornisce una traccia dello stack con la chiamata meno recente in basso.

trepan dipende da uncompyle6 disponibile here.

pydb fornisce una funzionalità simile ma sfortunatamente non è stato portato su Python3.

In caso contrario, è possibile decidere di essere pazienti e attendere miglioramenti. In 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

Si può fare con un po 'di hacking. These docs mostrare come è possibile attivare il debug post-mortem con il seguente codice nel punto di ingresso:

import sys 
from IPython.core import ultratb 
sys.excepthook = ultratb.FormattedTB(mode='Verbose', 
            color_scheme='Linux', call_pdb=1) 

Facendo un passo attraverso questo gancio dopo un'eccezione è sollevata dimostra che abbiamo bisogno di armeggiare con il metodo debugger. Sfortunatamente non vedo un modo migliore per farlo se non quello di copiare l'intero metodo e modificarlo dove necessario (ho provato a modificare self.tb ma gli oggetti di traceback sono di sola lettura e non possono essere usati con copy.deepcopy). Ecco una demo:

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

Come si può vedere, interrompe la ricerca attraverso il traceback quando sta per raggiungere il codice della libreria. Ecco il risultato:

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> 

Fondamentalmente il traceback completo è ancora stampato, ma parte da ipdb il proprio codice. Se si immette il comando down, ci si trova in una cornice della libreria.

Problemi correlati