2016-11-04 1 views
9

J'utilise la commande --pdb avec ipython, donc quand je débogue du code et qu'une erreur survient, elle affiche une trace de pile. Beaucoup de ces erreurs proviennent de l'appel de fonctions numpy ou pandas avec de mauvaises entrées. la trace de la pile commence à l'image la plus récente, en code à partir de ces bibliothèques. 5-10 répétitions de la commande up plus tard, je peux réellement voir ce que j'ai fait de mal, ce qui sera immédiatement évident 90% du temps (par exemple, appeler avec une liste au lieu d'un tableau).Démarrer le débogueur python dans la plus ancienne pile après une exception

Y a-t-il un moyen de spécifier à quelle image de pile le débogueur commence-t-il initialement? Soit le cadre de pile le plus ancien, soit le cadre de pile le plus récent dans le fichier python initialement lancé, ou similaire. Ce serait beaucoup plus productif pour le débogage.

Voici un exemple simple

import pandas as pd 

def test(df): # (A) 
    df[:,0] = 4 #Bad indexing on dataframe, will cause error 
    return df 

df = test(pd.DataFrame(range(3))) # (B) 

résultant retraçage, (A), (B), (C) ajouté pour plus de clarté

In [6]: --------------------------------------------------------------------------- 
TypeError         Traceback (most recent call last) 
<ipython-input-6-66730543fac0> in <module>() 
----> 1 import codecs, os;__pyfile = codecs.open('''/tmp/py29142W1d''', encoding='''utf-8''');__code = __pyfile.read().encode('''utf-8''');__pyfile.close();os.remove('''/tmp/py29142W1d''');exec(compile(__code, '''/test/stack_frames.py''', 'exec')); 

/test/stack_frames.py in <module>() 
     6 
     7 if __name__ == '__main__': 
(A)----> 8  df = test(pd.DataFrame(range(3))) 

/test/stack_frames.py in test(df) 
     2 
     3 def test(df): 
(B)----> 4  df[:,0] = 4 
     5  return df 
     6 

/usr/local/lib/python2.7/dist-packages/pandas/core/frame.pyc in __setitem__(self, key, value) 
    2355   else: 
    2356    # set column 
-> 2357    self._set_item(key, value) 
    2358 
    2359  def _setitem_slice(self, key, value): 

/usr/local/lib/python2.7/dist-packages/pandas/core/frame.pyc in _set_item(self, key, value) 
    2421 
    2422   self._ensure_valid_index(value) 
-> 2423   value = self._sanitize_column(key, value) 
    2424   NDFrame._set_item(self, key, value) 
    2425 

/usr/local/lib/python2.7/dist-packages/pandas/core/frame.pyc in _sanitize_column(self, key, value) 
    2602 
    2603   # broadcast across multiple columns if necessary 
-> 2604   if key in self.columns and value.ndim == 1: 
    2605    if (not self.columns.is_unique or 
    2606      isinstance(self.columns, MultiIndex)): 

/usr/local/lib/python2.7/dist-packages/pandas/indexes/base.pyc in __contains__(self, key) 
    1232 
    1233  def __contains__(self, key): 
-> 1234   hash(key) 
    1235   # work around some kind of odd cython bug 
    1236   try: 

TypeError: unhashable type 
> /usr/local/lib/python2.7/dist-packages/pandas/indexes/base.py(1234)__contains__() 
    1232 
    1233  def __contains__(self, key): 
(C)-> 1234   hash(key) 
    1235   # work around some kind of odd cython bug 
    1236   try: 

ipdb> 

maintenant idéalement, je voudrais que le débogueur pour commencer dans la deuxième image la plus ancienne en (B), ou même en (A). Mais certainement pas à (C) où il va par défaut.

+1

http: // stackoverflow.com/questions/37069323/stop-at-exception-in-my-not-bibliothèque-code peut être liée. –

Répondre

2

Réponse longue pour documenter le processus pour moi-même. solution semi-travail au fond:

Échec tentative ici:

import sys 
import pdb 
import pandas as pd 

def test(df): # (A) 
    df[:,0] = 4 #Bad indexing on dataframe, will cause error 
    return df 

mypdb = pdb.Pdb(skip=['pandas.*']) 
mypdb.reset() 

df = test(pd.DataFrame(range(3))) # (B) # fails. 

mypdb.interaction(None, sys.last_traceback) # doesn't work. 

Pdb skip documentation:

L'argument de saut, si on leur donne, doit être un itérables de modèles de nom du module de style glob. Le débogueur n'intervient pas dans les cadres qui proviennent d'un module qui correspond à l'un de ces modèles.

Pdb source code:

class Pdb(bdb.Bdb, cmd.Cmd): 

    _previous_sigint_handler = None 

    def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, 
       nosigint=False, readrc=True): 
     bdb.Bdb.__init__(self, skip=skip) 
     [...] 

# Post-Mortem interface 

def post_mortem(t=None): 
    # handling the default 
    if t is None: 
     # sys.exc_info() returns (type, value, traceback) if an exception is 
     # being handled, otherwise it returns None 
     t = sys.exc_info()[2] 
    if t is None: 
     raise ValueError("A valid traceback must be passed if no " 
         "exception is being handled") 

    p = Pdb() 
    p.reset() 
    p.interaction(None, t) 

def pm(): 
    post_mortem(sys.last_traceback) 

Bdb source code:

class Bdb: 
    """Generic Python debugger base class. 
    This class takes care of details of the trace facility; 
    a derived class should implement user interaction. 
    The standard debugger class (pdb.Pdb) is an example. 
    """ 

    def __init__(self, skip=None): 
     self.skip = set(skip) if skip else None 
    [...] 
    def is_skipped_module(self, module_name): 
     for pattern in self.skip: 
      if fnmatch.fnmatch(module_name, pattern): 
       return True 
     return False 

    def stop_here(self, frame): 
     # (CT) stopframe may now also be None, see dispatch_call. 
     # (CT) the former test for None is therefore removed from here. 
     if self.skip and \ 
       self.is_skipped_module(frame.f_globals.get('__name__')): 
      return False 
     if frame is self.stopframe: 
      if self.stoplineno == -1: 
       return False 
      return frame.f_lineno >= self.stoplineno 
     if not self.stopframe: 
      return True 
     return False 

Il est clair que la liste de saut est pas utilisé pour post-mortem. Pour résoudre ce problème, j'ai créé une classe personnalisée qui remplace la méthode d'installation.

import pdb 

class SkipPdb(pdb.Pdb): 
    def setup(self, f, tb): 
     # This is unchanged 
     self.forget() 
     self.stack, self.curindex = self.get_stack(f, tb) 
     while tb: 
      # when setting up post-mortem debugging with a traceback, save all 
      # the original line numbers to be displayed along the current line 
      # numbers (which can be different, e.g. due to finally clauses) 
      lineno = pdb.lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti) 
      self.tb_lineno[tb.tb_frame] = lineno 
      tb = tb.tb_next 

     self.curframe = self.stack[self.curindex][0] 
     # This loop is new 
     while self.is_skipped_module(self.curframe.f_globals.get('__name__')): 
      self.curindex -= 1 
      self.stack.pop() 
      self.curframe = self.stack[self.curindex][0] 
     # The rest is unchanged. 
     # The f_locals dictionary is updated from the actual frame 
     # locals whenever the .f_locals accessor is called, so we 
     # cache it here to ensure that modifications are not overwritten. 
     self.curframe_locals = self.curframe.f_locals 
     return self.execRcLines() 

    def pm(self): 
     self.reset() 
     self.interaction(None, sys.last_traceback) 

Si vous utilisez cela comme:

x = 42 
df = test(pd.DataFrame(range(3))) # (B) # fails. 
# fails. Then do: 
mypdb = SkipPdb(skip=['pandas.*']) 
mypdb.pm() 
>> <ipython-input-36-e420cf1b80b2>(2)<module>() 
>-> df = test(pd.DataFrame(range(3))) # (B) # fails. 
> (Pdb) l 
> 1 x = 42 
> 2 -> df = test(pd.DataFrame(range(3))) # (B) # fails. 
> [EOF] 

vous êtes tombé dans le cadre de droite. Maintenant vous juste besoin de comprendre comment ipython appelle leur fonction pdb pm/post_mortem, et créer un script similaire. Quel appears to be hard, donc je renonce à peu près ici.

Aussi, ce n'est pas une très bonne implémentation. Il suppose que les images que vous voulez ignorer sont au sommet de votre pile et produiront des résultats étranges. Par exemple. une erreur dans la fonction d'entrée de df.apply produira quelque chose de super bizarre. TLDR: Non pris en charge par stdlib, mais vous pouvez créer votre propre classe de débogage, mais il n'est pas trivial de l'utiliser avec le débogueur IPythons.

+0

Cela ressemble à l'appel de 'set_trace' lorsque la définition de la liste de choix est la syntaxe correcte, par ex. 'import pdb; pdb.Pdb (skip = ['django. *']). set_trace() ' – user2699

+0

Cela entrerait le débogueur au cadre de la pile appelante. Mais nous voulons prendre le dernier stacktrace où il s'est mal passé, et entrer le débogueur là. –