まあ、ここでは一種の遅いアプローチです。ローカル変数の変更を監視するために(名前だけで)変更できます。これはどのように動作するかです:sys.settraceを実行し、各ステップのobj.attrの値を分析します。難しい部分は、行が実行される前に'line'
イベント(いくつかの行が実行された)を受け取ることです。したがって、obj.attrが変更されたことに気付くと、既に次の行に入っています。前の行フレームを取得できません(各行にフレームがコピーされず、変更されているため)。したがって、各行イベントではtraceback.format_stack
をwatcher.prev_st
に保存し、次の呼び出しでtrace_command
の値が変更された場合は、保存されたスタックトレースをファイルに出力します。それぞれの行にトレースバックを保存するのは非常に高価な作業なので、include
キーワードをプロジェクトディレクトリのリスト(またはプロジェクトのルート)に設定すると、 CPU。
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()
はい、これは非常に遅いですが、手動のpdbよりもまだ高速です。ありがとうございます。 – Bunyk
ええ、修正済み。ちなみに、実際のものではなく次の行でOKならば、はるかに速い方法でこれをチェックすることができます。https://gist.github.com/4086770次の行または'line'イベントが' line'イベントに続くかどうかによって実際のものがあります –