2012-11-15 14 views
11

Istnieje duży projekt Pythona, w którym jeden atrybut jednej klasy ma w pewnym miejscu błędną wartość.Zobacz, jak zmienia się python

Powinien to być sqlalchemy.orm.attributes.InstrumentedAttribute, ale kiedy uruchomię testy, jest to wartość stała, powiedzmy ciąg.

Jest jakiś sposób uruchomienia programu Pythona w trybie debugowania i uruchomienia testu (jeśli zmieniono typ zmiennej) po każdym kroku przez linię kodu automatycznie?

P.S. Wiem, jak rejestrować zmiany atrybutu instancji klasy za pomocą inspekcji i dekoratora właściwości. Ewentualnie mogę tu użyć tej metody z metaclasses ...

Ale czasem muszę bardziej ogólnego rozwiązania i potężny ...

Dziękuję.

P.P.S. Potrzebuję czegoś podobnego: https://stackoverflow.com/a/7669165/816449, ale może być więcej wyjaśnień tego, co dzieje się w tym kodzie.

Odpowiedz

11

Oto podejście typu slow. Można go zmodyfikować do oglądania lokalnej zmiany zmiennej (tylko po nazwie). Oto jak to działa: wykonujemy sys.settrace i analizujemy wartość obj.attr każdego kroku. Najtrudniejszą częścią jest to, że otrzymujemy zdarzenia 'line' (że jakiś wiersz został wykonany) przed wykonaniem linii. Kiedy zauważyliśmy, że zmienił się obiekt obj.attr, jesteśmy już w kolejnej linii i nie możemy uzyskać poprzedniej ramki (ponieważ ramki nie są kopiowane dla każdej linii, są modyfikowane). Tak więc w każdym zdarzeniu liniowym zapisuję traceback.format_stack do watcher.prev_st i jeśli przy następnym wywołaniu wartości trace_command zmieniono, drukujemy zapisany stos do pliku. Zapisywanie tracebacka w każdym wierszu jest dość kosztowną operacją, więc musisz ustawić słowo kluczowe include na liście katalogów swoich projektów (lub po prostu na głównym katalogu twojego projektu), aby nie obserwować, jak inne biblioteki robią swoje rzeczy i marnują procesor.

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

Tak, jest to bardzo powolne, ale wciąż szybsze niż ręczne pdb, dziękuję. – Bunyk

+0

Tak, naprawione. Przy okazji, jeśli wszystko jest w porządku z następnym wierszem, a nie rzeczywistym, można to zrobić znacznie szybciej, sprawdź to: https://gist.github.com/4086770 pokazuje następną linię lub faktyczny, zależnie od tego, czy zdarzenie 'line' jest następstwem zdarzenia' line' –

1

Można użyć python debugger module (część biblioteki standardowej)

Aby go wykorzystać, tylko import WPB na początku pliku źródłowego:

import pdb 

a następnie ustawić ślad tam, gdzie chcesz rozpoczęcie sprawdzania kodu:

pdb.set_trace() 

następnie można zwiększyć za pomocą kodu z n i zbadać aktualny stan uruchamiając pytona com mands.

+1

Przepraszamy, zapomniałem dodać, chcę, aby ta myśl działała automatycznie. Tak więc uruchamiam debugger, nadaję mu swój stan, na przykład type (some.module.SomeClass.my_attribute) == str), i znajduję pierwszą linię, w której warunek nie jest spełniony. I są miliony linii kodu, i nie wiem, gdzie zmienna jest zmieniona. – Bunyk

1

Spróbuj użyć __setattr__. Documentation dla __setattr__

+0

Należy tu zwrócić uwagę na jedną rzecz - działa to tylko z atrybutami instancji klasy, która definiuje '__setattr__'. Aby użyć go z atrybutami klasy, musimy przedefiniować metaklasy dla klasy i kto wie, jakiej magii potrzebujemy, aby działała ze zmiennymi zdefiniowanymi w module. – Bunyk

+0

Tylko sugestia. –

Powiązane problemy