Właściwy sposób deklarowania niestandardowych WYJĄTKÓW w nowoczesnym Pythonie?

Jaki jest właściwy sposób deklarowania niestandardowych klas WYJĄTKÓW we współczesnym Pythonie? Moim głównym celem jest podążanie za standardem innych klas wyjątków, tak aby (na przykład) każdy dodatkowy ciąg, który dołączam do wyjątku, był drukowany przez dowolne narzędzie, które złapało wyjątek.

Przez "nowoczesny Python" mam na myśli coś, co będzie działać w Pythonie 2.5, ale będzie 'poprawne' dla Pythona 2.6 i Pythona 3.* sposób robienia rzeczy. I przez "custom" mam na myśli obiekt wyjątku, który może zawierać dodatkowe dane o przyczynie błędu: łańcuch znaków, może również jakiś inny dowolny obiekt istotny dla wyjątku.

W Pythonie 2.6.2 potknęło mnie następujące ostrzeżenie o deprecacji:

>>> 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

Wydaje się Szalone, że BaseException ma specjalne znaczenie dla atrybutów o nazwie message. Wnioskuję z PEP-352 ten atrybut miał specjalne znaczenie w 2.5, więc domyślam się, że ta nazwa (i tylko ta) jest teraz zabroniona? Ugh.

I ' m also zdaję sobie sprawę, że Exception ma jakiś magiczny parametr args, ale nigdy nie wiedziałem, jak go używać. Nie jestem też pewien, czy jest to właściwy sposób na robienie rzeczy w przyszłości; wiele dyskusji, które znalazłem w Internecie, sugerowało, że próbowali pozbyć się args w Pythonie 3.

Update: dwie odpowiedzi sugerują nadpisanie __init__, oraz __str__/__unicode__/__repr__. To wygląda na dużo pisania, czy to konieczne?

Author: Eitan T, 2009-08-24

7 answers

Może przeoczyłem pytanie, ale dlaczego nie:

class MyException(Exception):
    pass

Edit: aby nadpisać coś (lub przekazać dodatkowe args), zrób to:

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

W ten sposób można przekazać dict komunikatów o błędach do drugiego param, a następnie dostać się do niego za pomocą e.errors


Python 3 Update: w Pythonie 3+ możesz użyć tego nieco bardziej kompaktowego użycia 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
 968
Author: gahooa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-26 17:08:46

Z nowoczesnymi wyjątkami Pythona, nie musisz nadużywać .message, ani nadpisywać .__str__() lub .__repr__() ani żadnego z nich. Jeśli wszystko, co chcesz, to wiadomość informacyjna, gdy pojawi się wyjątek, zrób to:

class MyException(Exception):
    pass

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

To da traceback zakończony MyException: My hovercraft is full of eels.

Jeśli chcesz uzyskać większą elastyczność od wyjątku, możesz podać słownik jako argument:

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

Jednak dotarcie do tych szczegółów w bloku except jest nieco bardziej skomplikowane. Szczegóły są przechowywane w args atrybut, który jest listą. Trzeba by zrobić coś takiego:

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

Nadal można przekazać wiele elementów do wyjątku i uzyskać do nich dostęp za pomocą indeksów krotek, ale jest to wysoce odradzane (i było to nawet przeznaczone do deprecacji jakiś czas temu). Jeśli potrzebujesz więcej niż jednej informacji, a powyższa metoda nie jest dla ciebie wystarczająca, powinieneś podklasować Exception zgodnie z opisem w tutorialu .

class MyError(Exception):
    def __init__(self, message, animal):
        self.message = message
        self.animal = animal
    def __str__(self):
        return self.message
 376
Author: frnknstn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-10 08:02:43

"właściwy sposób deklarowania niestandardowych WYJĄTKÓW w nowoczesnym Pythonie?"

To jest w porządku, chyba że Twój wyjątek jest naprawdę rodzajem bardziej konkretnego wyjątku:]}
class MyException(Exception):
    pass

Lub lepiej (może idealnie), zamiast pass podaj docstring:

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

Podklasowanie Podklas WYJĄTKÓW

Z docs

Exception

Wszystkie wbudowane wyjątki spoza systemu pochodzą z tej klasy. Wszystkie zdefiniowane przez użytkownika wyjątki powinny być również wyprowadzane z tego klasy.

Oznacza to, że jeśli Twój wyjątek jest typem bardziej konkretnego wyjątku, podklasuj ten wyjątek zamiast ogólnego Exception (a rezultatem będzie to, że nadal wywodzisz się z Exception, jak zalecają dokumenty). Ponadto możesz przynajmniej podać docstring (i nie być zmuszonym do użycia słowa kluczowego pass):

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

Ustaw atrybuty, które tworzysz samodzielnie za pomocą niestandardowego __init__. Unikaj podawania dict jako pozycji argument, przyszli użytkownicy Twojego kodu będą Ci wdzięczni. Jeśli używasz przestarzałego atrybutu wiadomości, przypisanie go samemu uniknie 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) 

Naprawdę nie ma potrzeby, aby pisać własne __str__ lub __repr__. Wbudowane są bardzo ładne, a twoje spółdzielcze dziedzictwo zapewnia, że z niego korzystasz.

Krytyka najwyższej odpowiedzi

może przeoczyłem pytanie, ale dlaczego nie:

class MyException(Exception):
    pass

Ponownie problem z powyższym jest to, że aby go złapać, musisz albo nazwać go konkretnie (importując go, jeśli został utworzony w innym miejscu) lub złapać wyjątek, (ale prawdopodobnie nie jesteś przygotowany do obsługi wszystkich typów wyjątków i powinieneś tylko złapać wyjątki, które jesteś przygotowany do obsługi). Krytyka podobna do poniższej, ale dodatkowo nie jest to sposób inicjalizacji przez super, a otrzymasz DeprecationWarning, jeśli uzyskasz dostęp do atrybutu message:

Edit: aby nadpisać coś (lub przekazać dodatkowe args), zrób to:

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

W ten sposób można przekazać dict komunikatów o błędach do drugiego param i dostać się do niego później z e. errors

Wymaga również podania dokładnie dwóch argumentów (oprócz self.) Ani więcej, ani mniej. To interesujące ograniczenie, którego przyszli użytkownicy mogą nie docenić.

Być bezpośrednim-narusza substytucyjność Liskowa .

Zademonstruję oba błędy:

>>> 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'

W Porównaniu do:

>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'
 157
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-23 19:04:29

Zobacz, jak domyślnie działają wyjątki, jeśli jeden vs więcej atrybutów jest używanych (tracebacki pominięte):

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

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

Więc możesz chcieć mieć coś w rodzaju " szablon wyjątku ", działający jako sam wyjątek, w zgodny sposób:

>>> 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')

Można to łatwo zrobić za pomocą tej podklasy

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

A jeśli nie podoba Ci się ta domyślna reprezentacja krotki, po prostu Dodaj metodę __str__ do klasy ExceptionTemplate, Jak:

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

I będziesz miał

>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken
 41
Author: mykhal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-07 16:23:56

Powinieneś nadpisać metody __repr__ LUB __unicode__ zamiast używać wiadomości, args, które podasz podczas konstruowania wyjątku, będzie w atrybucie args obiektu wyjątku.

 15
Author: M. Utku ALTINKAYA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-18 05:11:01

Nie, "wiadomość" nie jest zabroniona. To po prostu przestarzałe. Aplikacja będzie działać dobrze z użyciem wiadomości. Ale możesz oczywiście pozbyć się błędu deprecjacji.

Podczas tworzenia niestandardowych klas wyjątków dla aplikacji, wiele z nich nie jest podklasowanych tylko z wyjątków, ale z innych, takich jak ValueError lub podobne. Następnie trzeba dostosować się do ich użycia zmiennych.

I jeśli masz wiele wyjątków w swojej aplikacji, zwykle dobrym pomysłem jest posiadanie wspólna niestandardowa klasa bazowa dla wszystkich z nich, dzięki czemu użytkownicy Twoich modułów mogą wykonywać

try:
    ...
except NelsonsExceptions:
    ...

I w takim przypadku możesz wykonać __init__ and __str__ potrzebne tam, więc nie musisz powtarzać tego dla każdego wyjątku. Ale po prostu wywołanie zmiennej message coś innego niż message robi sztuczkę.

W każdym razie, potrzebujesz __init__ or __str__ tylko wtedy, gdy robisz coś innego niż sam wyjątek. A ponieważ jeśli deprecjacja, następnie trzeba obu, lub masz błąd. To nie jest dużo dodatkowy kod potrzebny na zajęcia. ;)

 6
Author: Lennart Regebro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-23 21:58:47

Wypróbuj ten przykład

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
Author: Omkaar.K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-22 05:27:45