Programı durdurmadan tam geri izleme nasıl yazdırılır?


779

10 web sitesini ayrıştırır, veri dosyalarını bulur, dosyaları kaydeder ve sonra NumPy kitaplığında kolayca kullanılabilir veri yapmak için onları ayrıştırır bir program yazıyorum. Bu dosyanın kötü bağlantılar, zayıf biçimli XML, eksik girişler ve henüz kategorilere ayırmadığım diğer şeylerle karşılaştığı tonlarca hata var . Başlangıçta bu gibi hataları işlemek için bu programı yaptım:

try:
    do_stuff()
except:
    pass

Ama şimdi hataları günlüğe kaydetmek istiyorum:

try:
    do_stuff()
except Exception, err:
    print Exception, err

Bunun daha sonra incelenmek üzere bir günlük dosyasına yazdırıldığını unutmayın. Bu genellikle çok işe yaramaz veriler yazdırır. İstediğim, hata istisnayı durdurmadan denemeden tetiklendiğinde yazdırılan aynı satırları yazdırmaktır, ancak istediğim döngüler için bir dizi içinde yer aldığından programımı durdurmasını istemiyorum tamamlanmaya bakın.

Yanıtlar:


583

Başka bir cevap, geri izleme modülüne işaret etti .

İhbar ile unutmayınız print_exc, bazı köşe durumlarda, ne beklediğiniz elde olmayacaktır. Python 2.x'te:

import traceback

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_exc()

... son istisnanın izini gösterecektir :

Traceback (most recent call last):
  File "e.py", line 7, in <module>
    raise TypeError("Again !?!")
TypeError: Again !?!

Gerçekten orijinal geri izlemeye erişmeniz gerekiyorsa, bir çözüm, istisna bilgileriniexc_info yerel bir değişkende döndürülen şekilde önbelleğe almak ve aşağıdakileri kullanarak görüntülemektir print_exception:

import traceback
import sys

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        exc_info = sys.exc_info()

        # do you usefull stuff here
        # (potentially raising an exception)
        try:
            raise TypeError("Again !?!")
        except:
            pass
        # end of useful stuff


    finally:
        # Display the *original* exception
        traceback.print_exception(*exc_info)
        del exc_info

üreten:

Traceback (most recent call last):
  File "t.py", line 6, in <module>
    raise TypeError("Oups!")
TypeError: Oups!

Bununla birlikte birkaç tuzak:

  • Dokümanından sys_info:

    Bir istisna işleyen bir işlevde yerel değişkene geri izleme dönüş değeri atamak, döngüsel başvuruya neden olur . Bu, aynı işlevdeki bir yerel değişkenin veya geri izlemenin attığı her şeyin çöp toplanmasını önler. [...] Geri izlemeye ihtiyacınız varsa, kullandıktan sonra sildiğinizden emin olun (en iyi denemeyle yapılır ... sonunda ifade)

  • ancak aynı dokümandan:

    Python 2.2'den başlayarak, bu tür döngüler çöp toplama etkinleştirildiğinde otomatik olarak geri kazanılır ve erişilemez hale gelir, ancak döngü oluşturmamak için daha verimli kalır.


Öte yandan, bir istisna ile ilişkili geri izlemeye erişmenize izin vererek , Python 3 daha az şaşırtıcı bir sonuç üretir:

import traceback

try:
    raise TypeError("Oups!")
except Exception as err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_tb(err.__traceback__)

... gösterecek:

  File "e3.py", line 4, in <module>
    raise TypeError("Oups!")


258

Hata ayıklama yapıyorsanız ve yalnızca geçerli yığın izlemesini görmek istiyorsanız, şunu arayabilirsiniz:

traceback.print_stack()

Sadece tekrar yakalamak için bir istisnayı manuel olarak yükseltmeye gerek yoktur.


9
Geri izleme modülü tam olarak bunu yapar - bir istisna oluşturur ve yakalar.
pppery

3
Çıkış, varsayılan BTW olarak STDERR'a gider. Başka bir yere yönlendirildiğinden günlüklerimde görünmüyordu.
mpen

101

Programı durdurmadan tam geri izleme nasıl yazdırılır?

Programınızı bir hatayla durdurmak istemiyorsanız, bu hatayı bir dene / hariç tutmanız gerekir:

try:
    do_something_that_might_error()
except Exception as error:
    handle_the_error(error)

Tam geri izlemeyi ayıklamak için, tracebackmodülü standart kitaplıktan kullanacağız :

import traceback

Ve tam yığın izini aldığımızı göstermek için oldukça karmaşık bir yığın izi oluşturmak için:

def raise_error():
    raise RuntimeError('something bad happened!')

def do_something_that_might_error():
    raise_error()

Baskı

Geri izlemenin tamamını yazdırmak için şu traceback.print_excyöntemi kullanın :

try:
    do_something_that_might_error()
except Exception as error:
    traceback.print_exc()

Hangi baskılar:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Yazdırma, kaydetme işleminden daha iyi:

Bununla birlikte, en iyi uygulama modülünüz için bir günlükçünün ayarlanmasıdır. Modülün adını bilecek ve seviyeleri değiştirebilecektir (işleyiciler gibi diğer özellikler arasında)

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

Bu durumda, logger.exceptionbunun yerine işlevi isteyeceksiniz :

try:
    do_something_that_might_error()
except Exception as error:
    logger.exception(error)

Hangi günlükler:

ERROR:__main__:something bad happened!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Ya da belki sadece dizeyi istersiniz, bu durumda traceback.format_excişlevi yerine istersiniz :

try:
    do_something_that_might_error()
except Exception as error:
    logger.debug(traceback.format_exc())

Hangi günlükler:

DEBUG:__main__:Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Sonuç

Her üç seçenek için de, bir hatayla karşılaştığımız zamanla aynı çıktıyı aldığımızı görüyoruz:

>>> do_something_that_might_error()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

2
Yukarıda ve benim için de traceback.print_exc()sadece son çağrıyı döndürür: yığının birkaç seviyesini (ve muhtemelen tüm levele ler?) döndürmeyi nasıl başarıyorsunuz
herve-guerin

@geekobi Burada ne istediğini bilmiyorum. İzlemeyi programın / yorumlayıcının giriş noktasına ulaştığımızı göstereceğim. Ne hakkında net değilsiniz?
Aaron Hall

1
@Geekobi'nin söylediği şey, yakalar ve yeniden kaldırırsanız, traceback.print_exc (), orijinal yığını değil, yeniden yükseltme yığınını döndürür.
fizloki

@fizloki nasıl "yeniden yetiştiriyorsun"? Çıplak raiseveya istisna zincirleme mi yapıyorsunuz yoksa orijinal izlemeyi mi saklıyorsunuz? bkz. stackoverflow.com/questions/2052390/…
Aaron Hall

21

Birincisi, yapamaz kullanım printler kaydı için, kanıtlanmış ve bunu stdlib modülünü iyi düşünülmüş, kararsız vardır: logging. Kesinlikle gerekir bunun yerine kullanın.

İkincisi, bir yapmak cazip değil karışıklık yerli ve basit bir yaklaşım varken ilgisiz araçları ile. İşte burada:

log = logging.getLogger(__name__)

try:
    call_code_that_fails()
except MyError:
    log.exception('Any extra info you want to see in your logs')

Bu kadar. Artık işiniz bitti.

İşlerin başlık altında nasıl çalıştığıyla ilgilenen herkes için açıklama

log.exceptionGerçekte ne yapıyor, o zaman sadece bir çağrı log.error(yani, günlük olayı düzey ERROR) ve o zaman geri izleme yazdırın.

Neden daha iyi?

İşte bazı düşünceler:

  • Sadece bir sağ ;
  • açıktır;
  • basit.

Neden kimse tracebacklogger'ı kullanmamalı veya aramamalı exc_info=Trueveya ellerini kirletmemeli sys.exc_info?

Şey, çünkü! Hepsi farklı amaçlar için var. Örneğin, traceback.print_excçıktısı, tercümanın kendisi tarafından üretilen izlemelerden biraz farklıdır. Eğer kullanırsanız, günlüklerinizi okuyan herkesin kafasını karıştırırsınız, başlarını onlara vururlar.

exc_info=TrueGünlük aramalarına geçmek uygun değildir. Ancak , kurtarılabilir hataları yakalarken kullanışlıdır ve bunları (örneğin, INFOseviye kullanarak ) geri izleme ile de kaydetmek istersiniz, çünkü log.exceptionyalnızca bir düzeydeki günlükleri üretir - ERROR.

Ve kesinlikle sys.exc_infoolabildiğince uğraşmaktan kaçınmalısınız . Bu bir iç biri, sadece kamu arayüzü değil - yapabilirsiniz kesinlikle ne yaptığınızı biliyorsanız kullanın. Yalnızca yazdırma istisnaları için tasarlanmamıştır.


4
Ayrıca olduğu gibi çalışmaz. Bu değil. Şimdi yapmadım: bu cevap sadece zaman kaybettiriyor.
A.Rager

Ayrıca yapabileceğinizi de ekleyebilirim logging.exception(). Özel gereksinimleriniz yoksa günlük örneği oluşturmanıza gerek yoktur.
Shital Shah

9

@Aaron Hall'un cevabına ek olarak, giriş yapıyorsanız, ancak kullanmak istemiyorsanız logging.exception()(ERROR düzeyinde oturum açtığından ), daha düşük bir seviye ve geçiş kullanabilirsiniz exc_info=True. Örneğin

try:
    do_something_that_might_error()
except Exception:
    logger.info('General exception noted.', exc_info=True)

7

Eğer üzerinde herhangi bir deneme / istisna olmasaydı ortaya çıkarılan bir yığın olarak kesin yığın izlemesi elde etmek için, bunu rahatsız edici istisnayı yakalayan özel blok içine yerleştirmeniz yeterlidir.

desired_trace = traceback.format_exc(sys.exc_info())

Nasıl kullanılacağı aşağıda açıklanmıştır ( flaky_functanımlandığı varsayılır ve logfavori günlük sisteminizi çağırır):

import traceback
import sys

try:
    flaky_func()
except KeyboardInterrupt:
    raise
except Exception:
    desired_trace = traceback.format_exc(sys.exc_info())
    log(desired_trace)

KeyboardInterruptC'yi yakalamak ve yeniden yükseltmek iyi bir fikirdir , böylece programı hala Ctrl-C kullanarak öldürebilirsiniz. Günlüğe kaydetme, sorunun kapsamı dışındadır, ancak günlüğe kaydetme iyi bir seçenektir . Sys ve geri izleme modülleri için belgeler .


4
Bu Python 3'te çalışmaz ve değiştirilmesi gerekir desired_trace = traceback.format_exc(). sys.exc_info()Argüman olarak geçmek asla doğru bir şey değildi, ama Python 2'de sessizce göz ardı ediliyor - Python 3'te değil (3.6.4 zaten).
martineau

2
KeyboardInterruptdoğrudan veya dolaylı olarak türetilmemiştir Exception. (Her ikisi de türetilmiştir BaseException.) Bu except Exception:, asla a'yı yakalayamayacaktır KeyboardInterruptve bu nedenle except KeyboardInterrupt: raisetamamen gereksizdir.
AJNeufeld

traceback.format_exc(sys.exc_info())3.6.10
Nam G VU

6

Deneyin / haricinde hatanın meydana gelebileceği en iç döngü içine koymanız gerekir;

for i in something:
    for j in somethingelse:
        for k in whatever:
            try:
                something_complex(i, j, k)
            except Exception, e:
                print e
        try:
            something_less_complex(i, j)
        except Exception, e:
            print e

... ve bunun gibi

Başka bir deyişle, denemede / dışında mümkün olan en açık döngüde mümkün olan en iç döngüde başarısız olabilecek ifadeleri sarmanız gerekir.


6

Bu cevabın yorumları hakkında bir açıklama : print(traceback.format_exc())benim için daha iyi bir iş çıkarıyor traceback.print_exc(). İkincisi ile, hellobazen her ikisi de stdout veya stderr'e aynı anda yazmak, garip çıktı üretmek (en azından bir metin düzenleyicinin içinden inşa ederken ve çıktıyı "Sonuç oluştur" paneli).

Geri izleme (en son çağrı son olarak):
Dosya "C: \ Kullanıcılar \ Kullanıcı \ Masaüstü \ test.py", satır 7,
cehennemde do_stuff ()
Dosya "C: \ Kullanıcılar \ Kullanıcı \ Masaüstü \ test.py", satır 4 , do_stuff içinde
1/0
ZeroDivisionError: tamsayı bölme veya modulo sıfıra kadar
o
[ 0.1s ile tamamlandı ]

Bu yüzden kullanıyorum:

import traceback, sys

def do_stuff():
    1/0

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    print('hello')

5

Diğer cevapların hiçbirinde bunun belirtilmediğini görmüyorum. Herhangi bir nedenle bir İstisna nesnesinden geçiyorsanız ...

Python 3.5 ve sonraki sürümlerinde traceback.TracebackException.from_exception () yöntemini kullanarak bir Exception nesnesinden izleme alabilirsiniz . Örneğin:

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    try:
        stack_lvl_3()
    except Exception as e:
        # raise
        return e


def stack_lvl_1():
    e = stack_lvl_2()
    return e

e = stack_lvl_1()

tb1 = traceback.TracebackException.from_exception(e)
print(''.join(tb1.format()))

Ancak, yukarıdaki kod ile sonuçlanır:

Traceback (most recent call last):
  File "exc.py", line 10, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')
Exception: ('a1', 'b2', 'c3')

Bu, yığının sadece iki seviyesidir, ekranda yazdırılanın aksine istisna ortaya çıkarılmış stack_lvl_2()ve kesilmemiştir ( # raiseçizgiyi kaldırınız ).

Anladığım kadarıyla, bu durumda bir istisna yığının yükseltildiğinde yalnızca geçerli seviyesini kaydettiği için stack_lvl_3(). Yığın içinden geçtikçe, ona daha fazla seviye ekleniyor __traceback__. Ama onu ele geçirdik stack_lvl_2(), yani kaydetmesi gereken her şey seviye 3 ve 2 idi. Stdout'ta basıldığı gibi tam izi elde etmek için onu en yüksek (en düşük?) Seviyede yakalamamız gerekirdi:

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    stack_lvl_3()


def stack_lvl_1():
    stack_lvl_2()


try:
    stack_lvl_1()
except Exception as exc:
    tb = traceback.TracebackException.from_exception(exc)

print('Handled at stack lvl 0')
print(''.join(tb.stack.format()))

Sonuç:

Handled at stack lvl 0
  File "exc.py", line 17, in <module>
    stack_lvl_1()
  File "exc.py", line 13, in stack_lvl_1
    stack_lvl_2()
  File "exc.py", line 9, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')

Yığın baskısının farklı olduğuna, ilk ve son satırların eksik olduğuna dikkat edin. Çünkü bu farklıformat() .

İstisnayı mümkün olduğu kadar uzağa götürmek, daha fazla bilgi verirken daha kolay kodlama sağlar.


Bu önceki yöntemlerden çok daha iyi, ama yine de sadece bir yığın izi yazdırmak için gülünç kıvrımlı. Java daha az kod FGS alır.
elhefe

3

Geri izleme modülünü istiyorsunuz . Python'un yaptığı gibi yığın dökümlerini yazdırmanıza izin verir. Özellikle, print_last işlevi son istisnayı ve yığın izlemesini yazdırır.


3

Tam izlemeyi istisna nesnesinden dize olarak alın. traceback.format_exception

Yalnızca istisna nesneniz varsa, geri izlemeyi Python 3'teki kodun herhangi bir noktasından dize olarak alabilirsiniz:

import traceback

''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))

Tam örnek:

#!/usr/bin/env python3

import traceback

def f():
    g()

def g():
    raise Exception('asdf')

try:
    g()
except Exception as e:
    exc = e

tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)

Çıktı:

Traceback (most recent call last):
  File "./main.py", line 12, in <module>
    g()
  File "./main.py", line 9, in g
    raise Exception('asdf')
Exception: asdf

Belgeler: https://docs.python.org/3.7/library/traceback.html#traceback.format_exception

Ayrıca bkz: Bir istisna nesnesinden geri izleme bilgilerini ayıklama

Python 3.7.3'te test edilmiştir.


2

Zaten bir Error nesneniz varsa ve her şeyi yazdırmak istiyorsanız, bu biraz garip bir çağrı yapmanız gerekir:

import traceback
traceback.print_exception(type(err), err, err.__traceback__)

Bu doğru print_exceptionsürer üç pozisyonel argümanları: istisna, fiili durum nesnesi ve istisnanın kendi iç traceback özelliğinin türünü.

Python 3.5 veya sonraki sürümlerde, type(err)isteğe bağlıdır ... ancak bu konumsal bir argümandır, bu yüzden hala hiçbirini açıkça geçmeniz gerekir.

traceback.print_exception(None, err, err.__traceback__)

Tüm bunların neden sadece bir şey olmadığını bilmiyorum traceback.print_exception(err). Bir hatayı neden yazdırmak isteyesiniz ki, bu hataya ait olandan başka bir geri izleme ile birlikte, benden öte.

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.