2012-11-15 15 views
11

C'è un grande progetto python in cui un attributo di una classe ha solo un valore errato in qualche posto.Guarda per un cambiamento variabile in python

Deve essere sqlalchemy.orm.attributes.InstrumentedAttribute, ma quando eseguo i test è un valore costante, diciamo stringa.

C'è un modo per eseguire il programma python in modalità di debug ed eseguire alcuni controlli (se il tipo di variabile è cambiato) dopo ogni passaggio attraverso la riga di codice automaticamente?

P.S. So come registrare i cambiamenti dell'attributo dell'istanza di classe con l'aiuto di inspect e property decorator. Forse qui posso usare questo metodo con metaclassi ...

Ma a volte ho bisogno di più generale e potente soluzione ...

Grazie.

P.P.S. Ho bisogno di qualcosa del genere: https://stackoverflow.com/a/7669165/816449, ma potrebbe essere con ulteriori spiegazioni su cosa sta succedendo in quel codice.

risposta

11

Bene, ecco una sorta di approccio lento. Può essere modificato per la visualizzazione delle variabili locali (solo per nome). Ecco come funziona: facciamo sys.settrace e analizziamo il valore di obj.attr ad ogni passo. La parte difficile è che riceviamo gli eventi 'line' (che alcune linee sono state eseguite) prima che la linea venga eseguita. Quindi, quando notiamo che obj.attr è cambiato, siamo già sulla riga successiva e non possiamo ottenere il frame della riga precedente (perché i frame non vengono copiati per ogni riga, vengono modificati). Quindi su ogni evento di riga salverò traceback.format_stack a watcher.prev_st e se alla prossima chiamata del valore trace_command è stato modificato, stamperemo la traccia di stack salvata su file. Salvare il traceback su ogni linea è un'operazione piuttosto costosa, quindi dovresti impostare la parola chiave include in un elenco delle directory dei tuoi progetti (o solo la radice del tuo progetto) per non vedere come le altre biblioteche stanno facendo le loro cose e sprecano processore.

watcher.py

import traceback 

class Watcher(object): 
    def __init__(self, obj=None, attr=None, log_file='log.txt', include=[], enabled=False): 
     """ 
      Debugger that watches for changes in object attributes 
      obj - object to be watched 
      attr - string, name of attribute 
      log_file - string, where to write output 
      include - list of strings, debug files only in these directories. 
       Set it to path of your project otherwise it will take long time 
       to run on big libraries import and usage. 
     """ 

     self.log_file=log_file 
     with open(self.log_file, 'wb'): pass 
     self.prev_st = None 
     self.include = [incl.replace('\\','/') for incl in include] 
     if obj: 
      self.value = getattr(obj, attr) 
     self.obj = obj 
     self.attr = attr 
     self.enabled = enabled # Important, must be last line on __init__. 

    def __call__(self, *args, **kwargs): 
     kwargs['enabled'] = True 
     self.__init__(*args, **kwargs) 

    def check_condition(self): 
     tmp = getattr(self.obj, self.attr) 
     result = tmp != self.value 
     self.value = tmp 
     return result 

    def trace_command(self, frame, event, arg): 
     if event!='line' or not self.enabled: 
      return self.trace_command 
     if self.check_condition(): 
      if self.prev_st: 
       with open(self.log_file, 'ab') as f: 
        print >>f, "Value of",self.obj,".",self.attr,"changed!" 
        print >>f,"###### Line:" 
        print >>f,''.join(self.prev_st) 
     if self.include: 
      fname = frame.f_code.co_filename.replace('\\','/') 
      to_include = False 
      for incl in self.include: 
       if fname.startswith(incl): 
        to_include = True 
        break 
      if not to_include: 
       return self.trace_command 
     self.prev_st = traceback.format_stack(frame) 
     return self.trace_command 
import sys 
watcher = Watcher() 
sys.settrace(watcher.trace_command) 

testwatcher.py

from watcher import watcher 
import numpy as np 
import urllib2 
class X(object): 
    def __init__(self, foo): 
     self.foo = foo 

class Y(object): 
    def __init__(self, x): 
     self.xoo = x 

    def boom(self): 
     self.xoo.foo = "xoo foo!" 
def main(): 
    x = X(50) 
    watcher(x, 'foo', log_file='log.txt', include =['C:/Users/j/PycharmProjects/hello']) 
    x.foo = 500 
    x.goo = 300 
    y = Y(x) 
    y.boom() 
    arr = np.arange(0,100,0.1) 
    arr = arr**2 
    for i in xrange(3): 
     print 'a' 
     x.foo = i 

    for i in xrange(1): 
     i = i+1 

main() 
+0

Sì, questo è molto lento, ma comunque più veloce del pdb manuale, grazie. – Bunyk

+0

sì, risolto. A proposito, se stai bene con la riga successiva invece che con quella attuale, puoi farlo in un modo molto più veloce, controlla questo: https://gist.github.com/4086770 mostra la riga successiva o il reale, a seconda che l'evento 'line' segua l'evento' line' –

1

È possibile utilizzare la python debugger module (parte della libreria standard)

da usare, basta importare PDB nella parte superiore del file di origine:

import pdb 

e quindi impostare una traccia dove vuoi avviare ispezionare il codice:

pdb.set_trace() 

È quindi possibile eseguire il codice con n, e indagare lo stato corrente eseguendo python com mand.

+1

Spiacente, ho dimenticato di aggiungere, voglio che questo pensi funzioni automaticamente. Quindi avvio il debugger, gli do la mia condizione, ad esempio type (some.module.SomeClass.my_attribute) == str), e trova la prima riga dove la condizione non è soddisfatta. E ci sono milioni di righe di codice, e non so dove sia cambiata la variabile. – Bunyk

1

Provare a usare __setattr__. Documentation per __setattr__

+0

C'è una cosa da notare qui - questo funziona solo con gli attributi delle istanze di classe che definisce '__setattr__'. Per usarlo con gli attributi di classe, dobbiamo ridefinire il metaclasse per la classe e chissà quale magia abbiamo bisogno per farlo funzionare con le variabili definite nel modulo. – Bunyk

+0

Solo un suggerimento. –

Problemi correlati