2012-11-15 24 views
11

大きなPythonプロジェクトがあり、あるクラスの属性の1つが間違った値を持つ場所があります。Pythonの変数の変更を監視する

これはsqlalchemy.orm.attributes.InstrumentedAttributeでなければなりませんが、テストを実行するときは定数値です。文字列としましょう。

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

P.S.私はinspectとproperty decoratorの助けを借りてクラスインスタンスの属性の変更を記録する方法を知っています。おそらくここで私は...メタクラスで

を、この方法を使用することができます。しかし、時には、私は

ありがとう...より一般的で強力なソリューションを必要としています。

P.P.S.私はそこに何かが必要です:https://stackoverflow.com/a/7669165/816449、しかし、そのコードで何が起こっているの詳細な説明があります。

答えて

11

まあ、ここでは一種の遅いアプローチです。ローカル変数の変更を監視するために(名前だけで)変更できます。これはどのように動作するかです: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() 
+0

はい、これは非常に遅いですが、手動のpdbよりもまだ高速です。ありがとうございます。 – Bunyk

+0

ええ、修正済み。ちなみに、実際のものではなく次の行でOKならば、はるかに速い方法でこれをチェックすることができます。https://gist.github.com/4086770次の行または'line'イベントが' line'イベントに続くかどうかによって実際のものがあります –

1

あなたは

は自分のソースファイルの先頭にPDBをインポートし、使用するにはpython debugger module(標準ライブラリの一部)を使用することができます:あなたがしたいどこ

import pdb 

して、トレースを設定します

pdb.set_trace() 

あなたはその後、nでコードをステップ、およびCOMのpythonを実行することにより、現在の状態を調べることができます:コードを検査開始マンド。

+1

申し訳ありませんが、追加するのを忘れました、私はこれが自動的に動作すると思います。だから私はデバッガを起動し、条件(例えばsome.module.SomeClass.my_attribute)== str)を入力し、条件が満たされていない最初の行を見つけます。 数百万行のコードがあり、変数が変更される場所がわかりません。 – Bunyk

1

__setattr__を使用してみてください。 Documentation__setattr__

+0

ここで注意すべき点が1つあります - これは '__setattr__'を定義するクラスのインスタンスの属性でのみ動作します。クラス属性でそれを使用するには、クラスのメタクラスを再定義する必要があります。また、モジュールで定義された変数を使用するためにはどのような魔法が必要なのかを知っています。 – Bunyk

+0

ちょうど提案。 –