2012-11-15 3 views
11

Es gibt große Python-Projekt, wo ein Attribut einer Klasse nur an einer Stelle falschen Wert haben.Achten Sie auf eine variable Änderung in Python

Es sollte sqlalchemy.orm.attributes.InstrumentedAttribute, aber wenn ich Tests ausführen ist es konstanter Wert, sagen wir String.

Es gibt eine Möglichkeit, Python-Programm im Debug-Modus auszuführen, und führen Sie eine Überprüfung (wenn Variablen geändert Typ) nach jedem Schritt durch die Codezeile automatisch?

P.S. Ich weiß, wie man Änderungen des Attributs einer Klasseninstanz mithilfe von inspect und property decorator protokolliert. Möglicherweise hier kann ich diese Methode mit metaclasses verwenden ...

Aber manchmal brauche ich allgemeinere und leistungsfähige Lösung ...

Danke.

P.P.S. Ich brauche etwas wie dort: https://stackoverflow.com/a/7669165/816449, aber möglicherweise mit mehr Erklärung dessen, was in diesem Code vor sich geht.

Antwort

11

Nun, hier ist eine Art langsam Ansatz. Es kann geändert werden, um nach lokalen Variablenänderungen zu suchen (nur nach Namen). Und so funktioniert es: Wir machen sys.settrace und analysieren den Wert von obj.attr in jedem Schritt. Der schwierige Teil ist, dass wir 'line' Ereignisse (dass eine Zeile ausgeführt wurde) erhalten, bevor Zeile ausgeführt wird. Wenn wir feststellen, dass sich obj.attr geändert hat, befinden wir uns bereits in der nächsten Zeile, und wir können den vorherigen Zeilenframe nicht erhalten (da Frames nicht für jede Zeile kopiert werden, werden sie modifiziert). Also auf jeder Zeile Ereignis speichern ich traceback.format_stack zu watcher.prev_st und wenn beim nächsten Aufruf von trace_command Wert geändert hat, drucken wir die gespeicherte Stack-Trace in Datei. Das Speichern von Tracebacks in jeder Zeile ist ein ziemlich kostspieliger Vorgang. Daher müssen Sie das Schlüsselwort include in eine Liste Ihrer Projektverzeichnisse (oder einfach nur des Stammverzeichnisses Ihres Projekts) eingeben, um nicht zu sehen, wie andere Bibliotheken ihre Daten verarbeiten Zentralprozessor.

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

Ja, das ist sehr langsam, aber immer noch schneller als manuelle pdb, danke. – Bunyk

+0

ja, behoben. Übrigens, wenn Sie mit der nächsten Zeile statt der tatsächlichen Linie zufrieden sind, kann dies viel schneller erledigt werden, überprüfen Sie dies: https://gist.github.com/4086770 zeigt es entweder die nächste Zeile oder die Ist eine, abhängig davon, ob das 'line' Ereignis dem' line' Ereignis folgt –

1

Sie können den python debugger module (Teil der Standardbibliothek)

verwenden, importiert nur PDB an der Spitze der Quelldatei:

import pdb 

und stellen Sie dann eine Spur, wo immer Sie wollen starten Sie den Code Inspektion:

pdb.set_trace() 

Sie können dann mit n Schritt durch den Code, und untersuchen com den aktuellen Zustand von python läuft Mands.

+1

Sorry, ich habe vergessen hinzuzufügen, ich möchte dies denken, um automatisch zu arbeiten. Also starte ich den Debugger, gib ihm meine Bedingung, zum Beispiel type (some.module.SomeClass.my_attribute) == str), und finde die erste Zeile, wo die Bedingung nicht erfüllt ist. Und es gibt Millionen Zeilen Code, und ich weiß nicht, wo Variable geändert wird. – Bunyk

1

Versuchen __setattr__ verwenden. Documentation für __setattr__

+0

Hier ist eines zu beachten - das funktioniert nur mit Attributen von Instanzen der Klasse, die '__setattr__' definieren. Um es mit Klassenattributen zu verwenden, müssen wir die Metaklasse für die Klasse neu definieren, und wer weiß, welche Magie wir brauchen, um es mit den im Modul definierten Variablen arbeiten zu lassen. – Bunyk

+0

Nur ein Vorschlag. –

Verwandte Themen