Oto podejście typu slow. Można go zmodyfikować do oglądania lokalnej zmiany zmiennej (tylko po nazwie). Oto jak to działa: wykonujemy sys.settrace i analizujemy wartość obj.attr każdego kroku. Najtrudniejszą częścią jest to, że otrzymujemy zdarzenia 'line'
(że jakiś wiersz został wykonany) przed wykonaniem linii. Kiedy zauważyliśmy, że zmienił się obiekt obj.attr, jesteśmy już w kolejnej linii i nie możemy uzyskać poprzedniej ramki (ponieważ ramki nie są kopiowane dla każdej linii, są modyfikowane). Tak więc w każdym zdarzeniu liniowym zapisuję traceback.format_stack
do watcher.prev_st
i jeśli przy następnym wywołaniu wartości trace_command
zmieniono, drukujemy zapisany stos do pliku. Zapisywanie tracebacka w każdym wierszu jest dość kosztowną operacją, więc musisz ustawić słowo kluczowe include
na liście katalogów swoich projektów (lub po prostu na głównym katalogu twojego projektu), aby nie obserwować, jak inne biblioteki robią swoje rzeczy i marnują procesor.
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()
Tak, jest to bardzo powolne, ale wciąż szybsze niż ręczne pdb, dziękuję. – Bunyk
Tak, naprawione. Przy okazji, jeśli wszystko jest w porządku z następnym wierszem, a nie rzeczywistym, można to zrobić znacznie szybciej, sprawdź to: https://gist.github.com/4086770 pokazuje następną linię lub faktyczny, zależnie od tego, czy zdarzenie 'line' jest następstwem zdarzenia' line' –