2013-10-07 10 views
10

Pracuję nad skryptem, który potrwa kilka minut i chciałbym przekazać użytkownikowi pewne informacje o jego postępach. Niestety jestem wyjątkowo leniwy. chciałbym napisać funkcję bez rejestrowania, a następnie zastosować dekorator, który przechodzi przez funkcję i drukuje każdą linię przed wykonaniem tej linii. Zasadniczo co szukam jest loggingdecorator takie, że:Dekorator do zapisania linii wykonywania funkcji po linii

>>> @loggingdecorator 
... def myfunction(): 
...  foo() 
...  bar() 
...  baz() 
>>> myfunction() 
Starting myfunction 
foo() ... [OK] 
bar() ... [OK] 
baz() ... [OK] 
myfunction Done! 

Oto, co starałem dotąd:

import sys 


def logging_tracer(frame, event, arg): 
    def local_tracer(local_frame, event, arg): 
     if frame is local_frame: 
      print frame.f_code.co_name, event, arg 

    print frame.f_code.co_name, event, arg 
    return local_tracer 


def loggingdecorator(func): 
    def _wrapper(): 
     old_trace_function = sys.gettrace() 
     sys.settrace(logging_tracer) 
     try: 
      result = func() 
     except: 
      raise 
     else: 
      return result 
     finally: 
      sys.settrace(old_trace_function) 
    return _wrapper 

Niestety, ten wypisuje trochę za dużo; następuje po wywołaniach funkcji i wypisuje je, linia po linii, jak również (cóż, to właściwie nie drukuje linii źródłowej, istniejące odpowiedzi za pomocą inspekcji, w połączeniu z rzeczami na obiekcie ramki w funkcji śledzenia to robią), ale ja "Jestem nieco zaskoczony, jak logging_tracer, chyba że dana funkcja jest faktycznie dekorowana.

+0

Możesz umieścić ten sam dekorator na wszystkich funkcjach, do których chcesz zadzwonić. Następnie umieść instrukcję drukowania wewnątrz funkcji. W miarę wykonywania funkcji wykonywane są również instrukcje drukowania. Możesz też po prostu umieścić instrukcję drukowania w każdej funkcji bez obawy o dekorator i powinna ona nadal robić to samo: – smac89

+0

@ Smac89 Twoja sugestia jest zbyt inwazyjna. Wydruk debugowania jest bardzo złą techniką śledzenia – J0HN

+0

Najbliższa rzecz, o której myślę, to line_profiler. Prawdopodobnie możesz użyć podobnej metody, aby zrobić to, co chcesz. Niestety, line_profiler jest napisany w c/cython, więc nie jest to łatwe do modyfikacji. –

Odpowiedz

4

Oto, co wymyśliłem. @Corley Brigman - komentarz pozwolił mi zacząć we właściwym kierunku. To jest trochę hackey, sys.gettrace/settrace są raczej delikatnie opisywane jako "szczegóły implementacji CPython", więc nie należy oczekiwać, że to rozwiązanie będzie działało w innych implementacjach. Mimo to wydaje się, że działa całkiem dobrze. Funkcja śledzenia w cpythonie nie dostarcza żadnego powiadomienia o "zakończeniu wykonywania linii", więc [ok] z mojego pytania nie ma żadnego sensu.

Naprawienie problemu z rekurencyjnym śledzeniem jest po prostu kwestią trzymania pamięci podręcznej funkcji, które zostały udekorowane, a następnie generowania tylko wyników, jeśli śledzona ramka pochodzi z funkcji, która została udekorowana.

import inspect 
import sys 


def logging_tracer(frame, event, arg): 
    lines, firstline = inspect.getsourcelines(frame) 

    def local_tracer(local_frame, event, arg): 
     if event == 'line' and frame is local_frame: 
      print event, frame.f_lineno,'\t', lines[frame.f_lineno - firstline] 
      #print event, lines[frame.f_lineno - firstline] 
      #print frame.f_code.co_name, frame.f_lineno, event, arg 

    if frame.f_code in LOG_THESE_FUNCTIONS: 
     print event, frame.f_lineno,'\t', lines[frame.f_lineno - firstline + (event == 'call')] 
     #print frame.f_code.co_name, event, arg 
     return local_tracer 
    else: 
     return None 


LOG_THESE_FUNCTIONS = set() 


def loggingdecorator(func): 
    LOG_THESE_FUNCTIONS.add(func.func_code) 

    def _wrapper(): 
     old_trace_function = sys.gettrace() 
     sys.settrace(logging_tracer) 
     try: 
      result = func() 
     except: 
      raise 
     else: 
      return result 
     finally: 
      sys.settrace(old_trace_function) 
    return _wrapper 
0

można zrobić coś takiego:

>>> class loggingdecorator: 
...  def __init__(self, func): 
...    self.func = func 
...  def __call__(self): 
...    print "Entering", self.func.__name__ 
...    self.func() 
...    print "Exited", self.func.__name__ 
... 

Następnie z każdej z funkcji:

>>> @loggingdecorator 
... def func1(): 
...  print "Hello from func1(), how are you doing?" 
... 
>>> @loggingdecorator 
... def func2(): 
...  print "Hello from func2(), Whaddup Dawg...?" 
... 
>>> func1() 
Entering func1 
Hello from func1(), how are you doing? 
Exited func1 
>>> func2() 
Entering func2 
Hello from func2(), Whaddup Dawg...? 
Exited func2 

Znaleziony przykład z this page

+0

Niestety, nie do końca tego, czego szukam. Wyobraź sobie, że dekorowana funkcja ma dziesięć linii, a każda linia zajmuje 1 minutę. Chcę zobaczyć faktyczną linię źródłową wydrukowaną na konsoli tuż przed jej wykonaniem, dziesięć linii wyjściowych, każde oddzielone o około minutę upływającego czasu. – SingleNegationElimination

1

Oto brzydki przykład, który działa, jeśli istnieje tylko jeden poziom wcięcia:

import inspect 


def myDec(func): 
    temp = list(inspect.getsourcelines(func)[0]) 

    temp.append(' print("{} Done!")\n'.format(func.__name__)) 
    for index in range(len(temp)-2, 0, -1): 
     temp.insert(index+1, " print('''{}...[OK]''')\n".format(temp[index].strip().rstrip("\n"))) 
    temp.insert(1, ' print("Starting {}")\n'.format(func.__name__)) 
    temp = "".join(temp) 
    exec(temp) 
    return locals()[func.__name__] 

def foo(): 
    a = 4+5 
    list_int = range(100) 


foo = myDec(foo) 
foo() 

Naprawdę brzydki ...

Powiązane problemy