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 wszystkimi standardowymi innymi klasami wyjątków, aby (na przykład) każdy dodatkowy ciąg, który uwzględniam w wyjątku, był drukowany przez dowolne narzędzie, które wychwyciło wyjątek.
Przez „nowoczesny Python” rozumiem coś, co będzie działało w Pythonie 2.5, ale będzie „poprawne” dla Pythona 2.6 i Pythona 3. * sposób robienia rzeczy. I przez „niestandardowy” rozumiem obiekt wyjątku, który może zawierać dodatkowe dane o przyczynie błędu: ciąg znaków, może również jakiś inny dowolny obiekt związany z wyjątkiem.
Byłem zaskoczony przez następujące ostrzeżenie o wycofaniu w Pythonie 2.6.2:
>>> 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
To szalone, że BaseException
ma specjalne znaczenie dla nazwanych atrybutów message
. Wiem, że z PEP-352 ten atrybut miał specjalne znaczenie w 2.5, które starają się wycofać, więc myślę, że ta nazwa (i ta sama) jest teraz zabroniona? Ugh.
Jestem też dziwnie świadomy tego, ż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 postępów; wiele dyskusji, które znalazłem w Internecie, sugerowało, że próbowali pozbyć się argumentów w Pythonie 3.
Aktualizacja: dwie odpowiedzi sugerują zastąpienie __init__
i __str__
/ __unicode__
/ __repr__
. To chyba dużo pisania, czy to konieczne?
źródło
Wyjątki z nowoczesnych Python, nie trzeba do nadużyć
.message
lub ręcznym.__str__()
lub.__repr__()
lub którykolwiek z nim. Jeśli wszystko, czego potrzebujesz, to komunikat informujący o zgłoszeniu wyjątku, wykonaj następujące czynności:To da nam ślad kończący się na
MyException: My hovercraft is full of eels
.Jeśli chcesz uzyskać większą elastyczność od wyjątku, możesz przekazać słownik jako argument:
Jednak uzyskanie tych szczegółów w
except
bloku jest nieco bardziej skomplikowane. Szczegóły są przechowywane wargs
atrybucie, którym jest lista. Musisz zrobić coś takiego:Nadal możliwe jest przekazywanie wielu elementów do wyjątku i uzyskiwanie do nich dostępu za pośrednictwem indeksów krotek, ale jest to bardzo odradzane (a nawet kiedyś miało być wycofane). Jeśli potrzebujesz więcej niż jednej informacji, a powyższa metoda nie jest dla Ciebie wystarczająca, powinieneś podklasę
Exception
zgodnie z opisem w samouczku .źródło
Exception(foo, bar, qux)
.Jest to w porządku, chyba że twój wyjątek jest naprawdę bardziej szczegółowym wyjątkiem:
Lub lepiej (może idealnie), zamiast
pass
podawać dokumenty:Podklasy Wyjątek Podklasy
Z dokumentów
Oznacza to, że jeśli twój wyjątek jest rodzajem bardziej szczegółowego wyjątku, podklasuj ten wyjątek zamiast ogólnego
Exception
(a wynik będzie taki, że nadal wywodzisz się z niego,Exception
jak zalecają dokumenty). Możesz także podać dokument (i nie zmuszać Cię do używaniapass
słowa kluczowego):Ustaw atrybuty, które sam tworzysz za pomocą niestandardowego
__init__
. Unikaj przekazywania dyktusa jako argumentu pozycyjnego, przyszli użytkownicy Twojego kodu będą Ci wdzięczni. Jeśli użyjesz przestarzałego atrybutu wiadomości, samodzielne przypisanie go pozwoli uniknąćDeprecationWarning
:Naprawdę nie trzeba pisać własnego
__str__
lub__repr__
. Wbudowane są bardzo ładne, a twoje wspólne dziedzictwo zapewnia, że z niego korzystasz.Krytyka najwyższej odpowiedzi
Ponownie, problem z powyższym polega na tym, że aby go złapać, musisz albo nazwać go konkretnie (zaimportować, jeśli został utworzony gdzie indziej), albo złapać wyjątek (ale prawdopodobnie nie jesteś przygotowany do obsługi wszystkich rodzajów wyjątków, i powinieneś wychwytywać tylko wyjątki, na które jesteś przygotowany). Podobna krytyka do poniższej, ale dodatkowo nie jest to sposób inicjowania za pośrednictwem
super
, a otrzymaszDeprecationWarning
dostęp, jeśli uzyskasz dostęp do atrybutu wiadomości:Wymaga również przekazania dokładnie dwóch argumentów (oprócz
self
.) Nie więcej, nie mniej. To interesujące ograniczenie, którego przyszli użytkownicy mogą nie docenić.Mówiąc wprost - narusza to substytucyjność Liskova .
Zademonstruję oba błędy:
W porównaniu do:
źródło
BaseException.message
zniknął w Pythonie 3, więc krytyka dotyczy tylko starych wersji, prawda?__str__
metodęMyAppValueError
zamiast polegać namessage
atrybucieValueError
. Ma to sens, jeśli należy do kategorii błędów wartości. Jeśli nie jest to kategoria błędów wartości, sprzeciwiłbym się temu w semantyce. Jest miejsce na pewne niuanse i rozumowanie ze strony programisty, ale zdecydowanie wolę specyficzność, jeśli dotyczy. Niedługo zaktualizuję swoją odpowiedź, aby lepiej zająć się tym tematem.zobacz, jak domyślnie działają wyjątki, jeśli użyje się jednego atrybutu lub większej liczby (pominięto śledzenie):
więc możesz chcieć mieć coś w rodzaju „ szablonu wyjątku ”, działającego jako sam wyjątek, w zgodny sposób:
można to łatwo zrobić za pomocą tej podklasy
a jeśli nie podoba ci się ta domyślna reprezentacja krotkowa, po prostu dodaj
__str__
metodę doExceptionTemplate
klasy, na przykład:i będziesz miał
źródło
Począwszy od Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html ) zalecaną metodą jest nadal:
Nie zapomnij udokumentować, dlaczego wyjątek niestandardowy jest konieczny!
Jeśli potrzebujesz, możesz przejść do wyjątków z większą ilością danych:
i pobierz je jak:
payload=None
ważne jest, aby był zdolny do marynowania. Zanim go wyrzucisz, musisz zadzwonićerror.__reduce__()
. Ładowanie będzie działać zgodnie z oczekiwaniami.Być może powinieneś zbadać znalezienie rozwiązania przy użyciu
return
instrukcji pytonów , jeśli potrzebujesz dużo danych, aby przenieść je do jakiejś zewnętrznej struktury. Wydaje mi się to wyraźniejsze / bardziej pytoniczne. Zaawansowane wyjątki są często używane w Javie, co czasem może być denerwujące, gdy używa się frameworka i musi wychwycić wszystkie możliwe błędy.źródło
__str__
), a nie na inne odpowiedzi, które używająsuper().__init__(...)
. Po prostu wstyd, który zastępuje__str__
i__repr__
prawdopodobnie jest potrzebny tylko dla lepszego „domyślnego” serializacji.Powinieneś zastąpić
__repr__
lub__unicode__
metody zamiast używać wiadomości, argumenty podane podczas konstruowania wyjątku będą znajdować się wargs
atrybucie obiektu wyjątku.źródło
Nie, „wiadomość” nie jest zabroniona. To jest po prostu przestarzałe. Twoja aplikacja będzie działać poprawnie przy użyciu wiadomości. Ale oczywiście możesz pozbyć się błędu amortyzacji.
Podczas tworzenia niestandardowych klas wyjątków dla aplikacji wiele z nich nie jest podklasą tylko z wyjątku, ale z innych, takich jak ValueError lub podobny. Następnie musisz dostosować się do ich wykorzystania zmiennych.
A jeśli masz wiele wyjątków w swojej aplikacji, zwykle dobrym pomysłem jest utworzenie wspólnej niestandardowej klasy bazowej dla wszystkich z nich, aby użytkownicy modułów mogli to zrobić
I w takim przypadku możesz tam zrobić
__init__ and __str__
potrzebne, więc nie musisz powtarzać tego dla każdego wyjątku. Ale wystarczy wywołać zmienną message coś innego niż wiadomość.W każdym razie potrzebujesz tylko,
__init__ or __str__
jeśli zrobisz coś innego niż to, co robi sam wyjątek. A ponieważ w przypadku wycofania potrzebujesz obu tych elementów lub otrzymasz błąd. To nie jest dużo dodatkowego kodu, którego potrzebujesz na klasę. ;)źródło
Zobacz bardzo dobry artykuł „ Ostateczny przewodnik po wyjątkach w Pythonie ”. Podstawowe zasady to:
BaseException.__init__
tylko z jednym argumentem.Są też informacje o organizowaniu (w modułach) i pakowaniu wyjątków, polecam przeczytać przewodnik.
źródło
Always call BaseException.__init__ with only one argument.
Wydaje się, że jest to niepotrzebne ograniczenie, ponieważ w rzeczywistości akceptuje dowolną liczbę argumentów.Spróbuj tego przykładu
źródło
Aby poprawnie zdefiniować własne wyjątki, należy przestrzegać kilku najlepszych praktyk:
Zdefiniuj klasę podstawową dziedziczącą po
Exception
. Umożliwi to złapanie dowolnego wyjątku związanego z projektem (bardziej szczegółowe wyjątki powinny z niego dziedziczyć):Organizowanie tych klas wyjątków w osobnym module (np.
exceptions.py
) Jest ogólnie dobrym pomysłem.Aby przekazać dodatkowe argumenty do wyjątku, zdefiniuj niestandardową
__init__()
metodę ze zmienną liczbą argumentów. Wywołaj przekazanie przez klasę podstawową__init__()
wszelkich argumentów pozycyjnych (pamiętaj, żeBaseException
/Exception
spodziewaj się dowolnej liczby argumentów pozycyjnych ):Aby zgłosić taki wyjątek za pomocą dodatkowego argumentu, możesz użyć:
Ten projekt w rzeczywistości jest zgodny z zasadą podstawienia Liskowa , ponieważ można zastąpić wystąpienie podstawowej klasy wyjątku wystąpieniem pochodnej klasy wyjątku.
źródło