Python'da “iç istisna” (geri izleme ile)?


147

Benim arka plan C # ve yakın zamanda Python programlamaya başladım. Bir istisna atıldığında, genellikle tam yığın izini gösterirken daha fazla bilgi ekleyen başka bir istisnaya sarmak istiyorum. C # 'da oldukça kolay, ama Python'da nasıl yaparım?

Örneğin. C # ben böyle bir şey yapmak istiyorum:

try
{
  ProcessFile(filePath);
}
catch (Exception ex)
{
  throw new ApplicationException("Failed to process file " + filePath, ex);
}

Python'da benzer bir şey yapabilirim:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file ' + filePath, e)

... ama bu, iç istisnanın izini kaybeder!

Düzenleme: İstisna mesajları ve yığın izlerini görmek ve ikisini ilişkilendirmek istiyorum. Yani, çıktıda X istisnasının burada meydana geldiğini ve sonra da istisna Y'nin C # 'daki gibi göründüğünü görmek istiyorum. Bu Python 2.6'da mümkün mü? Şimdiye kadar yapabileceğim en iyi gibi görünüyor (Glenn Maynard'ın cevabına göre):

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2]

Bu, hem iletileri hem de geri izlemeleri içerir, ancak geri izlemenin neresinde hangi istisnanın oluştuğunu göstermez.


3
Kabul edilen cevap güncel değil, belki başka bir cevap almayı düşünmelisiniz.
Aaron Hall

1
@AaronHall maalesef OP 2015'ten beri görülmüyor.
Antti Haapala

Yanıtlar:


137

Python 2

Basit; izlemeyi üçüncü argüman olarak iletin.

import sys
class MyException(Exception): pass

try:
    raise TypeError("test")
except TypeError, e:
    raise MyException(), None, sys.exc_info()[2]

Bir istisnayı yakalarken ve bir diğerini tekrar yükseltirken her zaman bunu yapın.


4
Teşekkürler. Bu, geri izlemeyi korur, ancak özgün özel durumun hata iletisini kaybeder. Hem mesajları hem de geri izlemeleri nasıl görebilirim?
EMP

6
raise MyException(str(e)), ...Vs
Glenn Maynard

23
Python 3 ekler raise E() from tbve.with_traceback(...)
Dima Tisnek

3
@GlennMaynard oldukça eski bir soru, ama orta argümanı raiseistisnaya geçilecek değerdir (ilk argüman bir örnek değil istisna sınıfı ise). Yerine yapmanın, takas istisnalara istiyorsanız raise MyException(str(e)), None, sys.exc_info()[2], kullanmak daha iyidir: raise MyException, e.args, sys.exc_info()[2].
bgusach

8
Gelecek paketi kullanarak Python2 ve 3 uyumlu bir yol mümkündür: python-future.org/compatible_idioms.html#raising-exceptions Eg from future.utils import raise_ve raise_(ValueError, None, sys.exc_info()[2]).
jtpereyda

239

Python 3

Python 3'te aşağıdakileri yapabilirsiniz:

try:
    raise MyExceptionToBeWrapped("I have twisted my ankle")

except MyExceptionToBeWrapped as e:

    raise MyWrapperException("I'm not in a good shape") from e

Bu şöyle bir şey üretecektir:

   Traceback (most recent call last):
   ...
   MyExceptionToBeWrapped: ("I have twisted my ankle")

The above exception was the direct cause of the following exception:

   Traceback (most recent call last):
   ...
   MyWrapperException: ("I'm not in a good shape")

17
raise ... from ...Python 3'te bunu yapmanın doğru yolu budur.
Nakedible

NakedibleBence bunun nedeni maalesef çoğu insan hala Python 3 kullanmıyor.
Tim Ludwinski

Bu, python 3'te 'from' kullanarak bile oluyor gibi görünüyor
Steve Vermeulen

Python 2'ye destek verilebilir. Umarım bir gün olur.
Marcin Wojnarski

4
@ogrisel Bunu futurebaşarmak için paketi kullanabilirsiniz : python-future.org/compatible_idioms.html#raising-exceptions Eg from future.utils import raise_ve raise_(ValueError, None, sys.exc_info()[2]).
jtpereyda

19

Python 3, istisnaları zincirlemek için raise... frommaddesine sahiptir . Glenn'in cevabı Python 2.7 için harika, ancak sadece orijinal istisnanın izini kullanıyor ve hata mesajını ve diğer detayları atıyor. Python 2.7'de geçerli kapsamdan özgün özel durumun hata iletisine bağlam bilgileri ekleyen ancak diğer ayrıntıları olduğu gibi koruyan bazı örnekler.

Bilinen İstisna Türü

try:
    sock_common = xmlrpclib.ServerProxy(rpc_url+'/common')
    self.user_id = sock_common.login(self.dbname, username, self.pwd)
except IOError:
    _, ex, traceback = sys.exc_info()
    message = "Connecting to '%s': %s." % (config['connection'],
                                           ex.strerror)
    raise IOError, (ex.errno, message), traceback

Ve bu lezzet raisetablosu birinci ifade, ikinci ifadesi olarak bir demet içinde özel sınıfı Yapıcı ve üçüncü ifadesi olarak traceback olarak özel türü alır. Python 2.2'den daha önce çalışıyorsanız uyarılara bakın sys.exc_info().

İstisna Türleri

Kodunuzun ne tür istisnaları yakalaması gerekebileceğini bilmiyorsanız, daha genel bir amaç olan başka bir örnek. Dezavantajı, istisna türünü kaybetmesi ve sadece bir RuntimeError oluşturmasıdır. tracebackModülü içe aktarmanız gerekir .

except Exception:
    extype, ex, tb = sys.exc_info()
    formatted = traceback.format_exception_only(extype, ex)[-1]
    message = "Importing row %d, %s" % (rownum, formatted)
    raise RuntimeError, message, tb

Mesajı Değiştir

İstisna türü buna bağlam eklemenize izin veriyorsa, başka bir seçenek. Kural dışı durumun iletisini değiştirebilir ve daha sonra yeniden oluşturabilirsiniz.

import subprocess

try:
    final_args = ['lsx', '/home']
    s = subprocess.check_output(final_args)
except OSError as ex:
    ex.strerror += ' for command {}'.format(final_args)
    raise

Bu, aşağıdaki yığın izlemesi oluşturur:

Traceback (most recent call last):
  File "/mnt/data/don/workspace/scratch/scratch.py", line 5, in <module>
    s = subprocess.check_output(final_args)
  File "/usr/lib/python2.7/subprocess.py", line 566, in check_output
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory for command ['lsx', '/home']

check_output()Aranan satırı gösterdiğini görebilirsiniz , ancak istisna mesajı artık komut satırını içeriyor.


1
Nereden ex.strerrorgeliyor? Python belgelerinde bununla ilgili herhangi bir isabet bulamıyorum. Olmamalı mı str(ex)?
Henrik Heimbuerger

1
IOErrorve özniteliklerini EnvironmentErrorsağlayan @hheimbuerger öğesinden türetilir . errornostrerror
Don Kirkby

Nasıl bir keyfi Error, örneğin ValueError bir RuntimeErroryakalama içine sarmak Exception? Bu durum için cevabınızı yeniden oluşturursam, yığın izlemesi kaybolur.
Karl Richter

Ne istediğinden emin değilim, @ karl. Yeni bir soruya örnek gönderebilir ve daha sonra buradan bağlantı kurabilir misiniz?
Don Kirkby

OP sorusunun kopyasını doğrudan stackoverflow.com/questions/23157766/… adresinde doğrudan cevabınızı dikkate alarak bir tasfiye ile düzenledim. Orada tartışmalıyız :)
Karl Richter

12

In Python 3.x :

raise Exception('Failed to process file ' + filePath).with_traceback(e.__traceback__)

ya da basitçe

except Exception:
    raise MyException()

hangi yaymak olacaktır MyExceptionfakat baskı hem de işlenecek etmezse istisnalar.

In Python 2.x :

raise Exception, 'Failed to process file ' + filePath, e

__context__Özelliği öldürerek her iki istisnayı da yazdırabilirsiniz . Burada, istisnayı anında yakalamak ve değiştirmek için bunu kullanarak bir bağlam yöneticisi yazıyorum: ( nasıl çalıştıklarının açıklanması için http://docs.python.org/3.1/library/stdtypes.html adresine bakın )

try: # Wrap the whole program into the block that will kill __context__.

    class Catcher(Exception):
        '''This context manager reraises an exception under a different name.'''

        def __init__(self, name):
            super().__init__('Failed to process code in {!r}'.format(name))

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type is not None:
                self.__traceback__ = exc_tb
                raise self

    ...


    with Catcher('class definition'):
        class a:
            def spam(self):
                # not really pass, but you get the idea
                pass

            lut = [1,
                   3,
                   17,
                   [12,34],
                   5,
                   _spam]


        assert a().lut[-1] == a.spam

    ...


except Catcher as e:
    e.__context__ = None
    raise

4
TypeError: Yükselt: arg 3 bir geri izleme olmalı veya Hiçbiri
Glenn Maynard

Üzgünüm, bir hata yaptım, bir şekilde istisnaları da kabul ettiğini ve geri izleme özniteliklerini otomatik olarak aldığını düşündüm. Gereğince docs.python.org/3.1/reference/... , bu e .__ traceback__ olmalı
ilya n.

1
@ilyan .: Python 2'nin e.__traceback__özelliği yok!
Jan Hudec

5

Bunu Python 2.x'te yapabileceğinizi sanmıyorum, ancak bu işlevselliğe benzer bir şey Python 3'ün bir parçası. PEP 3134'ten :

Bugünün Python uygulamasında istisnalar üç bölümden oluşmaktadır: tür, değer ve geri izleme. 'Sys' modülü, geçerli istisnayı üç paralel değişkende gösterir, exc_type, exc_value ve exc_traceback, sys.exc_info () işlevi bu üç bölümden oluşan bir demet döndürür ve 'Yükselt' ifadesinin kabul eden üç bağımsız değişken formu vardır bu üç parça. İstisnaları değiştirmek, genellikle bu üç şeyi paralel olarak geçirmeyi gerektirir, bu da sıkıcı ve hataya açık olabilir. Ayrıca, 'hariç' ifadesi geri izleme için değil, sadece değere erişim sağlayabilir. İstisna değerlerine ' traceback ' özelliği eklemek, tüm istisna bilgilerine tek bir yerden erişilmesini sağlar.

C # ile Karşılaştırma:

C # özel durumları, başka bir özel duruma işaret edebilen salt okunur bir 'InnerException' özelliği içerir. Dokümantasyonu [10], "Önceki bir istisna Y'nin doğrudan bir sonucu olarak bir istisna X atıldığında, X'in InnerException özelliğinin Y'ye bir referans içermesi gerekir." Bu özellik VM tarafından otomatik olarak ayarlanmaz; bunun yerine, tüm istisna kurucuları açıkça ayarlamak için isteğe bağlı bir "innerException" argümanı alır. ' Sebep ' özniteliği InnerException ile aynı amacı yerine getirir, ancak bu PEP tüm istisnaların yapıcılarını genişletmek yerine yeni bir 'yükseltme' biçimi önerir. C # ayrıca doğrudan InnerException zincirinin sonuna atlar GetBaseException yöntemi sağlar;

Ayrıca Java, Ruby ve Perl 5'in de bu tür şeyleri desteklemediğini unutmayın. Tekrar alıntı yapıyorum:

Diğer dillere gelince, Java ve Ruby, 'catch' / 'rescue' veya 'nihayet' / 'sure' yan tümcesinde başka bir istisna oluştuğunda orijinal istisnayı atarlar. Perl 5, yerleşik yapılandırılmış istisna yönetiminden yoksundur. Perl 6 için RFC numarası 88 [9], @@ adlı bir dizideki zincirleme istisnaları örtülü olarak tutan bir istisna mekanizması önerir.


Ancak, elbette, Perl5'te sadece "itiraf qq {OH NOES! $ @}" Diyebilir ve diğer istisnanın yığın izini kaybedemezsiniz. Veya istisnayı koruyan kendi tipinizi uygulayabilirsiniz.
jrockway

4

Python 2 ve 3 arasındaki maksimum uyumluluk raise_fromiçin sixkütüphanede kullanabilirsiniz . https://six.readthedocs.io/#six.raise_from . İşte örneğiniz (netlik için biraz değiştirilmiş):

import six

try:
  ProcessFile(filePath)
except Exception as e:
  six.raise_from(IOError('Failed to process file ' + repr(filePath)), e)

3

CausedException sınıfımı Python 2.x'teki özel durumları zincirlemek için kullanabilirsiniz (ve hatta Python 3'te yeni oluşturulmuş bir özel duruma neden olarak birden fazla yakalanmış özel durum vermek istiyorsanız yararlı olabilir). Belki size yardımcı olabilir.


2

Belki ilgili bilgileri alıp aktarabilirsin? Şöyle bir şey düşünüyorum:

import traceback
import sys
import StringIO

class ApplicationError:
    def __init__(self, value, e):
        s = StringIO.StringIO()
        traceback.print_exc(file=s)
        self.value = (value, s.getvalue())

    def __str__(self):
        return repr(self.value)

try:
    try:
        a = 1/0
    except Exception, e:
        raise ApplicationError("Failed to process file", e)
except Exception, e:
    print e

2

varsayarsak:

  • Python 2 için çalışan bir çözüme ihtiyacınız var (saf Python 3 için raise ... fromçözüme bakın )
  • sadece hata mesajını zenginleştirmek istiyorum, örneğin ek bir bağlam sağlamak
  • tam yığın izine ihtiyacım var

https://docs.python.org/3/tutorial/errors.html#raising-exceptions dokümanlarından basit bir çözüm kullanabilirsiniz :

try:
    raise NameError('HiThere')
except NameError:
    print 'An exception flew by!' # print or log, provide details about context
    raise # reraise the original exception, keeping full stack trace

Çıktı:

An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

Anahtar parça, tek başına duran basitleştirilmiş 'yükselt' anahtar kelimesi gibi görünüyor. Bu, hariç blokta İstisna'yı yeniden yükseltir.


Bu Python 2 ve 3 uyumlu bir çözümdür! Teşekkürler!
Andy Chase

Bence fikir farklı bir istisna oluşturmaktı.
Tim Ludwinski

2
Bu, sadece bir istisnayı yeniden değerlendiren, iç içe geçmiş istisnalar zinciri değildir
Karl Richter

İstisna mesajını zenginleştirmeniz ve tam yığın izlemeniz gerekiyorsa, bu en iyi python 2 çözümüdür!
geekQ

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.