大きなpythonプロジェクトがあり、1つのクラスの1つの属性がどこかで間違った値を持っています。
Sqlalchemy.orm.attributes.InstrumentedAttributeである必要がありますが、テストを実行すると定数値、たとえば文字列になります。
pythonプログラムをデバッグモードで実行し、コードの各ステップの後に自動的にチェック(変数がタイプを変更した場合)を実行する方法はありますか?
P.S. inspectとプロパティデコレータの助けを借りて、クラスインスタンスの属性の変更をログに記録する方法を知っています。おそらくここでは、このメソッドをメタクラスで使用できます...
しかし、時にはもっと一般的で強力なソリューションが必要です...
ありがとうございました。
P.P.S.私はそこのようなものが必要です: https://stackoverflow.com/a/7669165/816449 、しかしそのコードで何が起こっているのかについてのより多くの説明があるかもしれません。
さて、ここに一種の遅いアプローチがあります。ローカル変数の変更を監視するように変更できます(名前だけで)。仕組みは次のとおりです。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()
__setattr__
を使用してみてください。 ドキュメント for __setattr__
pythonデバッガモジュール (標準ライブラリの一部)を使用できます
使用するには、ソースファイルの先頭にあるpdbをインポートするだけです。
import pdb
次に、コードの検査を開始する場所にトレースを設定します。
pdb.set_trace()
次に、n
を使用してコードをステップ実行し、pythonコマンドを実行して現在の状態を調査できます。
オブジェクトの属性の変更(モジュールレベルの変数またはgetattr
でアクセス可能なものでもかまいません)を監視する簡単な方法は、 hunter
ライブラリを活用することです。 柔軟なコードトレースツールキット。状態の変化を検出するには、次のような述語が必要です。
import traceback
class MutationWatcher:
def __init__(self, target, attrs):
self.target = target
self.state = {k: getattr(target, k) for k in attrs}
def __call__(self, event):
result = False
for k, v in self.state.items():
current_value = getattr(self.target, k)
if v != current_value:
result = True
self.state[k] = current_value
print('Value of attribute {} has chaned from {!r} to {!r}'.format(
k, v, current_value))
if result:
traceback.print_stack(event.frame)
return result
次に、サンプルコードを指定します。
class TargetThatChangesWeirdly:
attr_name = 1
def some_nested_function_that_does_the_nasty_mutation(obj):
obj.attr_name = 2
def some_public_api(obj):
some_nested_function_that_does_the_nasty_mutation(obj)
次のようにhunter
でインストルメントできます。
# or any other entry point that calls the public API of interest
if __name__ == '__main__':
obj = TargetThatChangesWeirdly()
import hunter
watcher = MutationWatcher(obj, ['attr_name'])
hunter.trace(watcher, stdlib=False, action=hunter.CodePrinter)
some_public_api(obj)
モジュールを実行すると、次のようになります。
Value of attribute attr_name has chaned from 1 to 2
File "test.py", line 44, in <module>
some_public_api(obj)
File "test.py", line 10, in some_public_api
some_nested_function_that_does_the_nasty_mutation(obj)
File "test.py", line 6, in some_nested_function_that_does_the_nasty_mutation
obj.attr_name = 2
test.py:6 return obj.attr_name = 2
... return value: None
action
がサポートする他のhunter
sを使用することもできます。たとえば、 Debugger
これはpdb
(属性変更のデバッガー)に分割されます。
def __setattr__(self, name, value):
if name=="xxx":
util.output_stack('xxxxx')
super(XXX, self).__setattr__(name, value)
このサンプルコードは私を助けてくれました。