web-dev-qa-db-ja.com

python dundersetattrまたはpdbなしで変数の変更を監視する方法

大きなpythonプロジェクトがあり、1つのクラスの1つの属性がどこかで間違った値を持っています。

Sqlalchemy.orm.attributes.InstrumentedAttributeである必要がありますが、テストを実行すると定数値、たとえば文字列になります。

pythonプログラムをデバッグモードで実行し、コードの各ステップの後に自動的にチェック(変数がタイプを変更した場合)を実行する方法はありますか?

P.S. inspectとプロパティデコレータの助けを借りて、クラスインスタンスの属性の変更をログに記録する方法を知っています。おそらくここでは、このメソッドをメタクラスで使用できます...

しかし、時にはもっと一般的で強力なソリューションが必要です...

ありがとうございました。

P.P.S.私はそこのようなものが必要です: https://stackoverflow.com/a/7669165/816449 、しかしそのコードで何が起こっているのかについてのより多くの説明があるかもしれません。

16
Bunyk

さて、ここに一種の遅いアプローチがあります。ローカル変数の変更を監視するように変更できます(名前だけで)。仕組みは次のとおりです。sys.settraceを実行し、各ステップでobj.attrの値を分析します。トリッキーな部分は、行が実行される前に'line'イベント(ある行が実行された)を受け取ることです。したがって、obj.attrが変更されていることに気付いた場合、すでに次の行にあり、前の行フレームを取得できません(フレームは行ごとにコピーされないため、変更されます)。したがって、各行イベントでtraceback.format_stackwatcher.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()
13
alex_jordan

__setattr__を使用してみてください。 ドキュメント for __setattr__

1
Solomon Ucko

pythonデバッガモジュール (標準ライブラリの一部)を使用できます

使用するには、ソースファイルの先頭にあるpdbをインポートするだけです。

import pdb

次に、コードの検査を開始する場所にトレースを設定します。

pdb.set_trace()

次に、nを使用してコードをステップ実行し、pythonコマンドを実行して現在の状態を調査できます。

1
Matt

オブジェクトの属性の変更(モジュールレベルの変数または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がサポートする他のhuntersを使用することもできます。たとえば、 Debugger これはpdb(属性変更のデバッガー)に分割されます。

1
saaj
def __setattr__(self, name, value):
    if name=="xxx":
       util.output_stack('xxxxx')
    super(XXX, self).__setattr__(name, value)

このサンプルコードは私を助けてくれました。

0
wangdq