Bir istisna nesnesinden geri izleme bilgisini ayıklayın


111

Bir İstisna nesnesi verildiğinde (kaynağı bilinmeyen), izini almanın bir yolu var mı? Bunun gibi bir kodum var:

def stuff():
   try:
       .....
       return useful
   except Exception as e:
       return e

result = stuff()
if isinstance(result, Exception):
    result.traceback <-- How?

Elime geçtikten sonra izlemeyi Exception nesnesinden nasıl çıkarabilirim?

Yanıtlar:


92

Bu sorunun cevabı, kullandığınız Python sürümüne bağlıdır.

Python 3'te

Çok basit: İstisnalar __traceback__, geri dönüşü içeren bir öznitelikle donatılmış olarak gelir . Bu öznitelik de yazılabilir ve with_tracebackistisnalar yöntemi kullanılarak rahatlıkla ayarlanabilir :

raise Exception("foo occurred").with_traceback(tracebackobj)

Bu özellikler, raisedokümantasyonun bir parçası olarak minimum düzeyde açıklanmıştır .

Cevabın bu kısmının tüm kredisi, bu bilgiyi ilk gönderen Vyctor'a gitmelidir . Bunu buraya dahil ediyorum çünkü bu cevap en üstte kalıyor ve Python 3 daha yaygın hale geliyor.

Python 2'de

Can sıkıcı derecede karmaşık. İzleme boşluklarıyla ilgili sorun, yığın çerçevelerine referansları olması ve yığın çerçevelerinin, referansları olan yığın çerçevelerine referansları olan izleme yığınlarına referansları olmasıdır ... fikri anladınız . Bu, çöp toplayıcı için sorunlara neden olur. ( Bunu ilk kez belirttiği için ecatmur'a teşekkürler .)

Bunu çözmenin güzel yolu , cümleyi terk ettikten sonra döngüyü cerrahi olarak kırmak olabilir,except Python 3'ün yaptığı da budur. Python 2 solüsyon daha çirkin: Bir ad-hoc fonksiyonu ile sağlanır sys.exc_info(), sadece içinde çalışır except maddesi . İstisnayı, istisna tipini ve halihazırda işlenen istisnanın izini içeren bir tuple döndürür.

Öyleyse, exceptcümlenin içindeyseniz, çeşitli yararlı şeyler yapmak sys.exc_info()için tracebackmodülle birlikte çıktısını kullanabilirsiniz :

>>> import sys, traceback
>>> def raise_exception():
...     try:
...         raise Exception
...     except Exception:
...         ex_type, ex, tb = sys.exc_info()
...         traceback.print_tb(tb)
...     finally:
...         del tb
... 
>>> raise_exception()
  File "<stdin>", line 3, in raise_exception

Ancak düzenlemenizin de belirttiği gibi, istisnanız zaten ele alındıktan sonra işlenmemiş olsaydı yazdırılacak olan traceback'i almaya çalışıyorsunuz . Bu çok daha zor bir soru. Ne yazık ki, hiçbir istisna işlenmediğinde geri döner . Diğer ilgili özellikler de yardımcı olmuyor. hiçbir istisna işlenmediğinde kullanımdan kaldırılır ve tanımlanmaz; mükemmel görünüyor, ancak sadece etkileşimli oturumlar sırasında tanımlanıyor gibi görünüyor.sys.exc_info(None, None, None)syssys.exc_tracebacksys.last_traceback

İstisnanın nasıl ortaya çıktığını kontrol edebiliyorsanız, bazı bilgileri depolamak inspectiçin özel bir istisna kullanabilirsiniz. Ama bunun nasıl çalışacağından tam olarak emin değilim.

Gerçeği söylemek gerekirse, bir istisnayı yakalamak ve geri vermek, yapılacak alışılmadık bir şeydir. Bu, yine de yeniden düzenlemeniz gerektiğinin bir işareti olabilir.


İstisnaları iade etmenin bir şekilde alışılmadık olduğuna katılıyorum, ancak bunun arkasındaki mantık için diğer soruma bakın .
georg

@ thg435, tamam, o zaman bu daha mantıklı. Diğer sorunuz için önerdiğim geri arama yaklaşımıylasys.exc_info birlikte kullanarak yukarıdaki çözümümü düşünün .
12'de


69

Yana Python 3.0 [3109 PEP] sınıf yerleşik Exceptionbir sahiptir __traceback__, bir içeren özelliği traceback object(Python 3.2.3):

>>> try:
...     raise Exception()
... except Exception as e:
...     tb = e.__traceback__
...
>>> tb
<traceback object at 0x00000000022A9208>

Sorun şu ki , bir süre Googling'den__traceback__ sonra sadece birkaç makale buldum, ancak hiçbiri kullanıp kullanmamanızı (kullanmamanızı) açıklamıyor __traceback__.

Bununla birlikte, Python 3 belgeleri şunuraise söylüyor:

Bir geri izleme nesnesi normalde bir istisna ortaya çıktığında ve __traceback__yazılabilir özellik olarak ona eklendiğinde otomatik olarak oluşturulur .

Bu yüzden kullanılması gerektiğini varsayıyorum.


4
Evet, kullanılması amaçlanmıştır. Gönderen Yenilikler In Python 3.0 "PEP 3134: İstisna artık onların Traceback saklamak nesnelerin traceback . () (Bir istisna nesnesi artık bir istisna ile ilgili bütün bilgileri içerdiğini Bu araçlar özniteliği ve sys.exc_info kullanmak daha az nedenler vardır ikincisi kaldırılmasa da). "
Maciej Szpakowski

Bu cevabın neden bu kadar tereddütlü ve şüpheli olduğunu gerçekten anlamıyorum. Belgelenmiş bir özelliktir; neden "kullanılmak üzere tasarlanmış" olmasın ?
Mark Amery

2
@MarkAmery Muhtemelen __bunun bir kamu mülkü değil, bir uygulama detayı olduğunu gösteren addaki ?
Temel

4
@Basic burada gösterdiği şey bu değil. Geleneksel olarak Python'da __fooözel bir yöntemdir, ancak __foo__(takip eden alt çizgilerle de) "sihirli" bir yöntemdir (özel değil).
Mark Amery

1
Bilginize, __traceback__öznitelik istediğiniz gibi kullanmak% 100 güvenlidir ve GC sonuçları yoktur. Bunu belgelerden söylemek zor, ancak ecatmur sağlam kanıtlar buldu .
gönderen

39

Python 3'te bir istisna nesnesinden dize olarak geri dönüş almanın bir yolu:

import traceback

# `e` is an exception object that you get from somewhere
traceback_str = ''.join(traceback.format_tb(e.__traceback__))

traceback.format_tb(...)dizelerin bir listesini döndürür. ''.join(...)onlara katılır. Daha fazla referans için lütfen şu adresi ziyaret edin: https://docs.python.org/3/library/traceback.html#traceback.format_tb


21

Bir kenara olarak, tam traceback'i terminalinize yazdırıldığını göreceğiniz gibi gerçekten almak istiyorsanız, şunu istersiniz:

>>> try:
...     print(1/0)
... except Exception as e:
...     exc = e
...
>>> exc
ZeroDivisionError('division by zero')
>>> tb_str = traceback.format_exception(etype=type(exc), value=exc, tb=exc.__traceback__)
>>> tb_str
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: division by zero\n']
>>> print("".join(tb_str))
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

format_tbYukarıdaki yanıtları kullanırsanız , daha az bilgi alacağınızı gösterir:

>>> tb_str = "".join(traceback.format_tb(exc.__traceback__))
>>> print("".join(tb_str))
  File "<stdin>", line 2, in <module>

4
En sonunda! Bu en iyi cevap olmalı. Teşekkür ederim Daniel!
Dany

3
Argh, bunu bulmadan önce son 20 dakikayı bunu çözmeye çalışarak geçirmiştim :-) etype=type(exc)şimdi göz ardı edilebilir btw: "3.5 sürümünde değiştirildi: etype argümanı yok sayılır ve değer türünden çıkarılır." docs.python.org/3.7/library/… Python 3.7.3'te test edildi.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

8

Geri dönüşün istisnada saklanmamasının çok iyi bir nedeni var; traceback, kendi yığının yerellerine referanslar tuttuğundan, bu döngüsel GC devreye girene kadar döngüsel bir referansa ve (geçici) bellek sızıntısına neden olur. (Bu nedenle , tracback'i asla yerel bir değişkende saklamamalısınız .)

Aklıma gelen tek şey, sizin için stuff'nin küresellerini maymun taraması yapmaktır, böylece yakaladığını düşündüğünde, Exceptionaslında özel bir türü yakalar ve istisna, arayan olarak size yayılır:

module_containing_stuff.Exception = type("BogusException", (Exception,), {})
try:
    stuff()
except Exception:
    import sys
    print sys.exc_info()

7
Bu yanlış. Python 3, traceback nesnesini istisnaya koyar e.__traceback__.
Glenn Maynard

6
@GlennMaynard Python 3 except, PEP 3110 başına bloktan çıkarken istisna hedefini silerek sorunu çözer .
ecatmur
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.