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()
Sì, questo è molto lento, ma comunque più veloce del pdb manuale, grazie. – Bunyk
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' –