2014-11-25 21 views
8

Chcę utworzyć traceback, taki jak zwrócony przez sys.exc_info() [2]. Nie chcę listy wierszy, chcę rzeczywisty obiekt śledzenia:Jak utworzyć obiekt traceback

<traceback object at 0x7f6575c37e48> 

Jak mogę to zrobić? Moim celem jest, aby zawierał bieżący stos minus jedną ramkę, więc wygląda na to, że osoba dzwoniąca jest ostatnią rozmową.

+0

Czego chcesz dla tego śledzenia? Wynik 'traceback.extract_stack' i/lub' inspect.stack' nie jest obiektem 'traceback', ale nie jest tylko listą linii. Czy jedno z nich jest wystarczające? – abarnert

+0

Zewnętrzna biblioteka przyjmuje tylko obiekt traceback i nie miałem ochoty go hakować. –

+0

"akceptuje tylko obiekt traceback" oznacza "checks" isinstance (t, types.TracebackType) '? Jeśli tak, cóż, nie byłbym zaskoczony, gdyby' traceback' był jedną z niewielu typów, które nie pozwolą sam być używany jako podstawa, ale najpierw sprawdziłbym co najmniej Jeśli nie, to co to znaczy? – abarnert

Odpowiedz

9

Nie ma udokumentowanego sposobu tworzenia obiektów traceback.

Żadna z funkcji modułu traceback nie tworzy ich. Oczywiście możesz uzyskać dostęp do tego typu jako types.TracebackType, ale jeśli zadzwonisz do jego konstruktora, otrzymasz numer TypeError: cannot create 'traceback' instances.

Powodem tego jest to, że tracebacki zawierają odniesienia do elementów wewnętrznych, do których nie można uzyskać dostępu lub generować z poziomu Pythona.


Można jednak uzyskać dostęp do ramek stosu, a wszystko inne, co potrzebne do symulacji przebiegu zwrotnego, jest trywialne. Można nawet napisać klasę, która ma atrybuty tb_frame, tb_lasti, i tb_next (korzystając z informacji, które można uzyskać z traceback.extract_stack i jednej z funkcji inspect), które będą wyglądały dokładnie jak ślad od dowolnego kodu czysto-Python.

So there's a good chance that whatever you really want to do is doable, even though what you're asking for is not.

+2

Jinja2 robi to hack plus kilka bardziej skomplikowanych rzeczy. https://github.com/mitsuhiko/jinja2/blob/ 9b4b20aa56fde3a5cd5ac49d4feacd96eacb832d/jinja2/debug.py – digenishjkl

+0

@digenishjkl Jest tam kilka fajnych rzeczy, w szczególności dynamiczne proxy do prawdziwego tracebacka przy dodaniu dodatkowej funkcjonalności to oczywisty dobry pomysł na wiele przypadków użycia, powinienem pomyśleć o tym ... – abarnert

+0

Przydaje się również przy budowaniu debuggerów w pytest, dzięki czemu możesz odfiltrować ramki '__tracebackhide__'. –

4

Jeśli naprawdę trzeba oszukać inną bibliotekę, zwłaszcza jeden napisany w C i przy użyciu niepubliczną API istnieją dwa potencjalne sposoby, aby uzyskać prawdziwy obiekt Traceback. Nie dostałem ani jednego, żeby działał niezawodnie. Obydwa są również specyficzne dla CPython, wymagają nie tylko korzystania z warstwy C API, ale także korzystania z nieudokumentowanych typów i funkcji, które mogą ulec zmianie w dowolnym momencie, i oferują potencjał nowych i ekscytujących możliwości segregowania interpretera. Ale jeśli chcesz spróbować, mogą być przydatne na początek.


Typ PyTraceBack nie jest częścią publicznego interfejsu API. Ale (poza tym, że jest zdefiniowany w katalogu Python zamiast w katalogu Object), jest zbudowany jako typ C API, ale nie jest udokumentowany. Tak więc, jeśli spojrzysz na traceback.h i traceback.c dla wersji Pythona, zobaczysz, że ... no cóż, nie ma PyTraceBack_New, ale istnieje jest a PyTraceBack_Here, który tworzy nowy traceback i zamienia go na bieżące informacje o wyjątku. Nie jestem pewien, czy można to wywoływać, chyba że istnieje obecny wyjątek, a jeśli istnieje jest bieżącym wyjątkiem, możesz go przekręcić przez mutowanie go w ten sposób, ale z pewnym zdarzeniem & awarii lub odczytaniem kodu, mam nadzieję, że można uzyskać to do pracy:

import ctypes 
import sys 

ctypes.pythonapi.PyTraceBack_Here.argtypes = (ctypes.py_object,) 
ctypes.pythonapi.PyTraceBack_Here.restype = ctypes.c_int 

def _fake_tb(): 
    try: 
     1/0 
    except: 
     frame = sys._getframe(2) 
     if ctypes.pythonapi.PyTraceBack_Here(frame): 
      raise RuntimeError('Oops, probably hosed the interpreter') 
     raise 

def get_tb(): 
    try: 
     _fake_tb() 
    except ZeroDivisionError as e: 
     return e.__traceback__ 

Jako alternatywę zabawy, możemy starać się mutować obiekt traceback w locie. Aby uzyskać obiekt Traceback, po prostu podnieść i złapać wyjątek:

try: 1/0 
except exception as e: tb = e.__traceback__ # or sys.exc_info()[2] 

Jedynym problemem jest to, że wskazując na ramce stosu, nie twój rozmówca, prawda?Jeśli tracebacki były zmienne, możesz to łatwo naprawić:

tb.tb_lasti, tb.tb_lineno = tb.tb_frame.f_lasti, tb.tb_frame.f_lineno 
tb.tb_frame = tb.tb_frame.f_back 

Nie ma też metod ustawiania tych rzeczy. Zauważ, że nie ma ona setattro, a jej getattro działa przez budowanie w locie __dict__, więc oczywiście jedynym sposobem, w jaki uzyskujemy te rzeczy, jest podstawowa struktura. Które naprawdę należy budować z ctypes.Structure, ale jako szybki Hack:

p8 = ctypes.cast(id(tb), ctypes.POINTER(ctypes.c_ulong)) 
p4 = ctypes.cast(id(tb), ctypes.POINTER(ctypes.c_uint)) 

Teraz, dla normalnego 64-bitowy build CPython, p8[:2]/p4[:4] to normalny przedmiot nagłówek, a potem przyjść traceback specyficznych pola, więc p8[3] jest tb_frame, a p4[8] i p4[9] są odpowiednio tb_lasti i tb_lineno. Więc:

p4[8], p4[9] = tb.tb_frame.f_lasti, tb.tb_frame.f_lineno 

Ale następna część jest nieco trudniejsze, ponieważ tb_frame nie jest faktycznie PyObject *, to tylko surowe struct _frame *, więc hajda do frameobject.h, gdzie można zobaczyć, że jest to naprawdę PyFrameObject * tak możesz ponownie użyć tej samej sztuczki. Po prostu pamiętaj o _ctypes.Py_INCREF następnej klatce ramki i po zmianie przypisania p8[3] na pf8[3], lub gdy tylko spróbujesz wydrukować traceback, utkniesz i stracisz całą pracę, którą zrobiłeś, pisząc to. :)

Powiązane problemy