Nun, hier ist eine Art langsam Ansatz. Es kann geändert werden, um nach lokalen Variablenänderungen zu suchen (nur nach Namen). Und so funktioniert es: Wir machen sys.settrace und analysieren den Wert von obj.attr in jedem Schritt. Der schwierige Teil ist, dass wir 'line'
Ereignisse (dass eine Zeile ausgeführt wurde) erhalten, bevor Zeile ausgeführt wird. Wenn wir feststellen, dass sich obj.attr geändert hat, befinden wir uns bereits in der nächsten Zeile, und wir können den vorherigen Zeilenframe nicht erhalten (da Frames nicht für jede Zeile kopiert werden, werden sie modifiziert). Also auf jeder Zeile Ereignis speichern ich traceback.format_stack
zu watcher.prev_st
und wenn beim nächsten Aufruf von trace_command
Wert geändert hat, drucken wir die gespeicherte Stack-Trace in Datei. Das Speichern von Tracebacks in jeder Zeile ist ein ziemlich kostspieliger Vorgang. Daher müssen Sie das Schlüsselwort include
in eine Liste Ihrer Projektverzeichnisse (oder einfach nur des Stammverzeichnisses Ihres Projekts) eingeben, um nicht zu sehen, wie andere Bibliotheken ihre Daten verarbeiten Zentralprozessor.
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()
Ja, das ist sehr langsam, aber immer noch schneller als manuelle pdb, danke. – Bunyk
ja, behoben. Übrigens, wenn Sie mit der nächsten Zeile statt der tatsächlichen Linie zufrieden sind, kann dies viel schneller erledigt werden, überprüfen Sie dies: https://gist.github.com/4086770 zeigt es entweder die nächste Zeile oder die Ist eine, abhängig davon, ob das 'line' Ereignis dem' line' Ereignis folgt –