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. :)
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
Zewnętrzna biblioteka przyjmuje tylko obiekt traceback i nie miałem ochoty go hakować. –
"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