Modern Python'da özel istisnalar bildirmenin doğru yolu?


1289

Modern Python'da özel istisna sınıfları bildirmenin doğru yolu nedir? Öncelikli hedefim, diğer istisna sınıflarının standartlarını takip etmek, böylece istisnayı eklediğim herhangi bir ekstra dize istisnayı yakalayan herhangi bir araç tarafından yazdırılmaktır.

"Modern Python" ile Python 2.5'te çalışacak, ancak Python 2.6 ve Python 3 * için 'doğru' olacak bir şeyleri kastediyorum. Ve "custom" demek Hatanın nedeni hakkında fazladan veri içerebilen bir Exception nesnesini kastediyorum: bir string, belki de istisna ile ilgili başka bir keyfi nesne.

Python 2.6.2'de aşağıdaki kullanımdan kaldırma uyarısı ile açıldım:

>>> class MyError(Exception):
...     def __init__(self, message):
...         self.message = message
... 
>>> MyError("foo")
_sandbox.py:3: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6

BaseExceptionAdlandırılmış özellikler için özel bir anlamı olan deli gibi görünüyor message. Ben toplamak PEP-352 I (ve yalnız biri o) artık yasak bu ismi tahmin böylece, nitelik onlar uzakta kaldırmayı çalışıyoruz 2.5 özel bir anlama sahip ki? Ugh.

Ayrıca Exceptionbazı sihirli parametrelere sahip olduğunu bilmiyorum args, ama nasıl kullanılacağını hiç bilmiyordum. Ayrıca eminim ki ileriye doğru bir şeyler yapmanın doğru yolu budur; İnternette bulduğum tartışmaların çoğu Python 3'teki argümanlarla uğraşmaya çalıştıklarını ileri sürdü.

Güncelleme: iki cevap geçersiz kılmayı önerdi __init__ve __str__/ __unicode__/ __repr__. Bu çok yazmak gibi görünüyor, gerekli mi?

Yanıtlar:


1322

Belki soruyu kaçırdım, ama neden olmasın:

class MyException(Exception):
    pass

Düzenleme: bir şeyi geçersiz kılmak (veya fazladan değişkenler iletmek) için şunu yapın:

class ValidationError(Exception):
    def __init__(self, message, errors):

        # Call the base class constructor with the parameters it needs
        super(ValidationError, self).__init__(message)

        # Now for your custom code...
        self.errors = errors

Bu şekilde hata mesajlarını dikte etmeyi ikinci parametreye aktarabilir ve daha sonra e.errors


Python 3 Güncellemesi: Python 3 + 'da, bu biraz daha kompakt kullanımı kullanabilirsiniz super():

class ValidationError(Exception):
    def __init__(self, message, errors):

        # Call the base class constructor with the parameters it needs
        super().__init__(message)

        # Now for your custom code...
        self.errors = errors

35
Ancak, bu şekilde tanımlanan bir istisna seçilemez; buradaki tartışmaya bakın stackoverflow.com/questions/16244923/…
jiakai

86
@jiakai "toplanabilir" anlamına gelir. :-)
Robino

1
Kullanıcı tanımlı özel durumlar için python belgelenmesini takiben, __init__ işlevinde belirtilen adlar yanlıştır. (Benlik, mesaj, hata) yerine (benlik, ifade, mesaj). Öznitelik ifadesi, hatanın oluştuğu giriş ifadesidir ve mesaj, hatanın bir açıklamasıdır.
ddleon

2
Bu bir yanlış anlama, @ddleon. Bahsettiğiniz dokümanlardaki örnek, belirli bir kullanım durumu içindir. Alt sınıfın yapıcı argümanlarının (ya da sayılarının) adının bir önemi yoktur.
asthasr

498

Modern Python İstisnalar ile, istismar gerekmez .messageveya geçersiz kılma .__str__()veya .__repr__()veya herhangi. İstediğiniz tek şey istisnanız ortaya çıktığında bilgilendirici bir mesajsa, bunu yapın:

class MyException(Exception):
    pass

raise MyException("My hovercraft is full of eels")

Bu, biten bir iz bırakacaktır MyException: My hovercraft is full of eels.

İstisnadan daha fazla esneklik istiyorsanız, bir sözlüğü argüman olarak iletebilirsiniz:

raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})

Bununla birlikte, bir exceptblokta bu ayrıntılara ulaşmak biraz daha karmaşıktır. Ayrıntılar argsbir liste olan öznitelikte saklanır . Bunun gibi bir şey yapmanız gerekir:

try:
    raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
except MyException as e:
    details = e.args[0]
    print(details["animal"])

İstisnaya birden fazla öğe aktarmak ve bunlara tuple indeksleri aracılığıyla erişmek hala mümkündür, ancak bu son derece cesaret kırılmıştır (ve bir süre önce kullanımdan kaldırılmak üzere tasarlanmıştır). Tek bir bilgiden daha fazlasına ihtiyacınız varsa ve yukarıdaki yöntem sizin için yeterli değilse Exception, öğreticide açıklandığı gibi alt sınıf yapmalısınız .

class MyError(Exception):
    def __init__(self, message, animal):
        self.message = message
        self.animal = animal
    def __str__(self):
        return self.message

2
"ancak bu gelecekte kullanımdan kaldırılacak" - bu hala kullanımdan kaldırılmaya yönelik mi? Python 3.7 hala mutlu bir şekilde kabul ediyor gibi görünüyor Exception(foo, bar, qux).
mtraceur

Geçiş ağrısından dolayı son girişim başarısız olduğu için, bunu yakınlaştırmak için yakın zamanda herhangi bir çalışma görmemiştir, ancak bu kullanım hala önerilmez. Bunu yansıtmak için cevabımı güncelleyeceğim.
frnknstn

@frnknstn, neden cesaretiniz kırıldı? Benim için hoş bir deyim gibi görünüyor.
neves

2
@neves bir başlangıç ​​için, istisna bilgilerini saklamak için tuples kullanmak, aynı şeyi yapmak için bir sözlük kullanma üzerinde hiçbir yararı yoktur. İstisna değişikliklerinin ardındaki mantıkla ilgileniyorsanız, PEP352
frnknstn

PEP352'nin ilgili bölümü "Geri Alınan Fikirler" dir .
liberforce

196

İnternethaber.com "Modern Python'da özel istisnalar bildirmenin uygun yolu var mı?"

İstisneniz gerçekten daha spesifik bir istisna türü değilse, bu iyidir:

class MyException(Exception):
    pass

Veya daha iyi (belki de mükemmel), passbir doktora vermek yerine :

class MyException(Exception):
    """Raise for my specific kind of exception"""

Alt Sınıf İstisna Alt Sınıfları

Gönderen docs

Exception

Tüm yerleşik, sistemden çıkmayan istisnalar bu sınıftan türetilir. Kullanıcı tanımlı tüm istisnalar da bu sınıftan türetilmelidir.

Bunun anlamı şu , eğer istisna bir daha spesifik bir istisna tipi, yerine jenerik bir istisna olduğu alt sınıfıdır Exception(hala kaynaklandığını ve sonuç olacak Exceptiondokümanlar tavsiye gibi). Ayrıca, en azından bir doktora sağlayabilir (ve passanahtar kelimeyi kullanmak zorunda kalmazsınız ):

class MyAppValueError(ValueError):
    '''Raise when my specific value is wrong'''

Kendinizi bir özel ile oluşturduğunuz özellikleri ayarlayın __init__. Bir dikteyi konumsal bir argüman olarak iletmekten kaçının, kodunuzun gelecekteki kullanıcıları size teşekkür edecektir. Kullanımdan kaldırılmış ileti özniteliğini kullanırsanız, kendiniz atamanız aşağıdakilerden kaçınacaktır DeprecationWarning:

class MyAppValueError(ValueError):
    '''Raise when a specific subset of values in context of app is wrong'''
    def __init__(self, message, foo, *args):
        self.message = message # without this you may get DeprecationWarning
        # Special attribute you desire with your Error, 
        # perhaps the value that caused the error?:
        self.foo = foo         
        # allow users initialize misc. arguments as any other builtin Error
        super(MyAppValueError, self).__init__(message, foo, *args) 

Kendinizinkini yazmaya gerek yok __str__ya da __repr__. Yerleşik olanlar çok güzel ve kooperatif mirasınız onu kullanmanızı sağlar.

En iyi cevabın eleştirisi

Belki soruyu kaçırdım, ama neden olmasın:

class MyException(Exception):
    pass

Yine, yukarıdaki sorun, onu yakalamak için, onu özel olarak adlandırmanız (başka bir yerde oluşturulmuşsa içe aktarma) veya İstisna'yı yakalamanız gerekmesidir (ancak muhtemelen her türlü İstisna türünü ele almaya hazır değilseniz, ve yalnızca ele almaya hazır olduğunuz istisnaları yakalamanız gerekir). Aşağıda benzer eleştiri, fakat buna ek olarak en değil yolu üzerinden başlatmak için olduğunu superve bir alırsınız DeprecationWarningileti özelliğini erişmek eğer:

Düzenleme: bir şeyi geçersiz kılmak (veya fazladan değişkenler iletmek) için şunu yapın:

class ValidationError(Exception):
    def __init__(self, message, errors):

        # Call the base class constructor with the parameters it needs
        super(ValidationError, self).__init__(message)

        # Now for your custom code...
        self.errors = errors

Bu şekilde hata mesajlarının diksiyonunu ikinci parametreye aktarabilir ve daha sonra e.errors ile alabilirsiniz.

Ayrıca, tam olarak iki argümanın iletilmesini (. Dışında self) gerektirir Daha fazla, daha az değil. Bu, gelecekteki kullanıcıların takdir edemeyeceği ilginç bir kısıtlamadır.

Doğrudan olmak - Liskov'un ikame edilebilirliğini ihlal ediyor .

Her iki hatayı da göstereceğim:

>>> ValidationError('foo', 'bar', 'baz').message

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    ValidationError('foo', 'bar', 'baz').message
TypeError: __init__() takes exactly 3 arguments (4 given)

>>> ValidationError('foo', 'bar').message
__main__:1: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
'foo'

Nazaran:

>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'

2
2018'den merhaba! BaseException.messagePython 3'te gitti, bu yüzden eleştiri sadece eski versiyonlar için geçerli, değil mi?
Kos

5
@Kos Liskov Değiştirilebilirliği ile ilgili eleştiri hala geçerlidir. İlk argümanın "mesaj" olarak semantiği de tartışmalı bir şekilde tartışmalıdır, ama konuyu tartışacağımı sanmıyorum. Daha fazla boş zamanım olduğunda buna daha fazla bakacağım.
Aaron Hall

1
FWIW, Python 3 (3.6+ için en az), bir yeniden tanımlamak olacaktır __str__yöntemini MyAppValueErroryerine güvenmek messageözniteliği
Jacquot

1
@AaronHall İstisna yerine ValueError alt sınıflandırma avantajını genişletebilir misiniz? Bunun dokümanlar tarafından kastedildiğini belirtiyorsunuz, ancak doğrudan bir okuma bu yorumu desteklemiyor ve Python Tutorial'da Kullanıcı Tanımlı İstisnalar altında bunu açıkça kullanıcı seçimi yapıyor: "İstisnalar genellikle İstisna sınıfından türetilmelidir, doğrudan veya dolaylı olarak. " Bu nedenle, görüşünüzün haklı olup olmadığını anlamak isteyin, lütfen.
ostergaard

1
@ostergaard Şu anda tam olarak cevap veremiyorum, ancak kısacası kullanıcı ek yakalama seçeneği elde ediyor ValueError. Değer Hataları kategorisinde olması mantıklıdır. Değer Hataları kategorisinde değilse, anlambilimde buna karşı çıkarım. Programcı tarafında biraz nüans ve muhakeme için yer var, ancak uygulanabilir olduğunda çok özgüllüğü tercih ediyorum. Bir süre sonra konuyla daha iyi başa çıkmak için cevabımı güncelleyeceğim.
Aaron Hall

50

biri varsa istisnalar varsayılan olarak nasıl çalıştığını görmek vs birden fazla özelliğe (tracebacks atlanmıştır) kullanılır:

>>> raise Exception('bad thing happened')
Exception: bad thing happened

>>> raise Exception('bad thing happened', 'code is broken')
Exception: ('bad thing happened', 'code is broken')

bu nedenle , bir istisna olarak çalışan bir tür " istisna şablonu " nu uyumlu bir şekilde kullanmak isteyebilirsiniz :

>>> nastyerr = NastyError('bad thing happened')
>>> raise nastyerr
NastyError: bad thing happened

>>> raise nastyerr()
NastyError: bad thing happened

>>> raise nastyerr('code is broken')
NastyError: ('bad thing happened', 'code is broken')

bu, bu alt sınıfla kolayca yapılabilir

class ExceptionTemplate(Exception):
    def __call__(self, *args):
        return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass

ve varsayılan grup benzeri gösterimi beğenmediyseniz __str__, ExceptionTemplatesınıfa aşağıdaki gibi bir yöntem ekleyin :

    # ...
    def __str__(self):
        return ': '.join(self.args)

ve sahip olacaksın

>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken

32

Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html ) itibarıyla önerilen yöntem hala:

class CustomExceptionName(Exception):
    """Exception raised when very uncommon things happen"""
    pass

Lütfen özel bir istisnanın neden gerekli olduğunu belgelemeyi unutmayın!

Gerekirse, daha fazla veri içeren istisnalara gitmenin yolu budur:

class CustomExceptionName(Exception):
    """Still an exception raised when uncommon things happen"""
    def __init__(self, message, payload=None):
        self.message = message
        self.payload = payload # you could add more args
    def __str__(self):
        return str(self.message) # __str__() obviously expects a string to be returned, so make sure not to send any other data types

ve aşağıdaki gibi getirin:

try:
    raise CustomExceptionName("Very bad mistake.", "Forgot upgrading from Python 1")
except CustomExceptionName as error:
    print(str(error)) # Very bad mistake
    print("Detail: {}".format(error.payload)) # Detail: Forgot upgrading from Python 1

payload=Noneturşu yapabilmesi önemlidir. Boşaltmadan önce aramalısınızerror.__reduce__() . Yükleme beklendiği gibi çalışacaktır.

returnBazı dış yapılara aktarılacak çok fazla veriye ihtiyacınız varsa, pythons deyimini kullanarak bir çözüm bulma konusunda araştırma yapmalısınız . Bu benim için daha açık / pitonik görünüyor. Gelişmiş istisnalar, bir çerçeve kullanırken ve olası tüm hataları yakalamak zorunda kaldığında, bazen rahatsız edici olabilecek Java'da yoğun olarak kullanılır.


1
En azından, mevcut dokümanlar bu __str__diğer yanıtlar yerine (en azından olmadan ) bunu yapmak için bir yol olduğunu gösterir super().__init__(...).. Sadece daha iyi "varsayılan" serileştirme için geçersiz kılan __str__ve __repr__muhtemelen gerekli bir utanç .
kevlarr

2
Dürüst soru: İstisnaların turşu yapabilmesi neden önemlidir? Boşaltma ve yükleme istisnaları için kullanım durumları nelerdir?
Roel Schroeven

1
@RoelSchroeven: Kodu bir kez paralelleştirmek zorunda kaldım. İyi bir tek işlem gerçekleştirdi, ancak bazı sınıflarının yönleri serileştirilemedi (lambda işlevi nesne olarak iletildi). Anlamak ve düzeltmek için biraz zaman aldı. Birisi daha sonra kodunuzu serileştirmek, bunu yapamamak ve neden kazmak zorunda kalabilirsiniz ... Benim sorunum alınamayan hatalar değildi, ama benzer sorunlara neden olduğunu görebilirsiniz.
logicOnAbstractions

17

İleti kullanmak yerine geçersiz kılmanız __repr__veya __unicode__yöntemleri kullanmanız gerekir ; kural dışı durumu oluştururken sağladığınız argümanlar args, kural dışı durum nesnesinin özelliğinde olur.


7

Hayır, "mesaj" yasaktır. Sadece reddedildi. Uygulamanız mesaj kullanarak iyi çalışacaktır. Ancak, elbette, kullanımdan kaldırma hatasından kurtulmak isteyebilirsiniz.

Uygulamanız için özel İstisna sınıfları oluşturduğunuzda, birçoğu yalnızca İstisna'dan değil, ValueError veya benzeri başkalarından alt sınıflar oluşturur. O zaman değişkenlerin kullanımına uyum sağlamanız gerekir.

Ve uygulamanızda birçok istisna varsa, hepsi için ortak bir özel taban sınıfına sahip olmak iyi bir fikirdir, böylece modüllerinizin kullanıcıları yapabilir

try:
    ...
except NelsonsExceptions:
    ...

Ve bu durumda __init__ and __str__ gerekli olanı orada yapabilirsiniz, böylece her istisna için tekrarlamanız gerekmez. Ama sadece mesaj değişkeni mesajdan başka bir şey çağırmak hile yapar.

Her durumda, yalnızca __init__ or __str__İstisna'nın kendisinden farklı bir şey yaparsanız gerekir . Ve çünkü kullanımdan çıkarılma durumunda, ikisine de ihtiyacınız vardır veya bir hata alırsınız. Bu, sınıf başına ihtiyacınız olan bir sürü ekstra kod değildir. ;)


Django istisnalarının ortak bir temelden miras kalmaması ilginçtir. docs.djangoproject.com/tr/2.2/_modules/django/core/exceptions Belirli bir uygulamadaki tüm istisnaları yakalarken iyi bir örneğiniz var mı? (belki de yalnızca belirli uygulama türleri için yararlıdır).
Yaroslav Nikitenko

Bu konuda iyi bir makale buldum, julien.danjou.info/python-exceptions-guide . İstisnaların, uygulama tabanlı değil, öncelikle etki alanı tabanlı olması gerektiğini düşünüyorum. Uygulamanız HTTP protokolüyle ilgili olduğunda, HTTPError öğesinden türetilir. Uygulamanızın bir kısmı TCP olduğunda, söz konusu parçanın istisnalarını TCPError'dan alırsınız. Ancak uygulamanız çok fazla alana (dosya, izin vb.) Yayılıyorsa, MyBaseException özelliğinin nedeni azalır. Yoksa 'katman ihlali'nden korunmak mı?
Yaroslav Nikitenko

6

Çok iyi bir makaleye bakın " Python istisnaları için kesin kılavuz ". Temel ilkeler:

  • Her zaman (en azından) İstisnadan miras alın.
  • Her zaman BaseException.__init__sadece bir argümanla arayın .
  • Bir kitaplık oluştururken, İstisnadan devralınan bir temel sınıf tanımlayın.
  • Hata hakkında ayrıntılı bilgi verin.
  • Mantıklı olduğunda yerleşik istisna türlerinden devralın.

Düzenleme (modüllerde) ve istisnalar hakkında bilgi de var, kılavuzu okumanızı tavsiye ederim.


1
Bu neden SO üzerinde genellikle en çok oylanan cevabı, ama en son cevapları kontrol iyi bir örnek. Yararlı ek, teşekkürler.
logicOnAbstractions

1
Always call BaseException.__init__ with only one argument.Aslında herhangi bir sayıda argümanı kabul ettiği için gereksiz bir kısıtlama gibi görünüyor .
Eugene Yarmash

@EugeneYarmash Katılıyorum, şimdi bunu anlamıyorum. Zaten kullanmıyorum. Belki de makaleyi tekrar okumalı ve cevabımı genişletmeliyim.
Yaroslav Nikitenko

@EugeneYarmash Makaleyi tekrar okudum. Birkaç argüman olması durumunda C uygulamasının "return PyObject_Str (self-> args);" Bu, bir dizenin birkaç dizeden daha iyi çalışması gerektiği anlamına gelir. Kontrol ettin mi?
Yaroslav Nikitenko

3

Bu Örneği Deneyin

class InvalidInputError(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return repr(self.msg)

inp = int(input("Enter a number between 1 to 10:"))
try:
    if type(inp) != int or inp not in list(range(1,11)):
        raise InvalidInputError
except InvalidInputError:
    print("Invalid input entered")

1

Kendi istisnalarınızı doğru bir şekilde tanımlamak için izlemeniz gereken birkaç en iyi uygulama vardır:

  • Kaynağından devralınan bir temel sınıf tanımlayın Exception. Bu, proje ile ilgili herhangi bir istisnayı yakalamaya izin verecektir (daha spesifik istisnalar projeden miras almalıdır):

    class MyProjectError(Exception):
        """A base class for MyProject exceptions."""

    Bu istisna sınıflarını ayrı bir modülde (örn. exceptions.py) Düzenlemek genellikle iyi bir fikirdir.

  • Özel bir istisna oluşturmak için temel istisna sınıfını alt sınıflayın.

  • Özel bir özel duruma fazladan bağımsız değişken (ler) desteği eklemek için, __init__()değişken sayıda bağımsız değişken içeren özel bir yöntem tanımlayın . __init__()Herhangi bir konum bağımsız değişkenini aktararak temel sınıfın çağrısını yapın ( herhangi bir sayıda konum bağımsız değişkenini unutmayın BaseException/ Exceptionbekleyin )

    class CustomError(MyProjectError):
        def __init__(self, *args, **kwargs):
            super(CustomError, self).__init__(*args)
            self.foo = kwargs.get('foo')

    Böyle bir istisnayı ekstra bir argümanla yükseltmek için şunları kullanabilirsiniz:

    raise CustomError('Something bad happened', foo='foo')

Bir temel istisna sınıfının bir örneğini, türetilmiş bir istisna sınıfının bir örneğiyle değiştirebileceğinizden, bu tasarım Liskov ikame ilkesine bağlı kalır . Ayrıca, üst öğe ile aynı parametrelere sahip türetilmiş bir sınıf örneği oluşturmanıza olanak tanır.

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.