Custom exceptions
Picture a warehouse where every problem gets the same sign: "Error". The tea ran out—"Error". A typo in the delivery note—also "Error". The storekeeper sees the sign but does not know whether to run for a new batch or fix the papers.
The built-in ValueError is such a general sign: int("ten") raises it, and so do hundreds of other functions. If a warehouse raises a ValueError when an item runs out, callers cannot tell that case from a typo in a number. You need a sign of your own with exact wording.
A custom exception is a class that inherits from Exception. A descendant of a class is also called its subclass:
class OutOfStockError(Exception):
pass
def take(stock, name):
if stock.get(name, 0) == 0:
raise OutOfStockError(f"no {name} left")
stock[name] -= 1
stock = {"tea": 0}
try:
take(stock, "tea")
except OutOfStockError as e:
print("error:", e) # error: no tea leftLet's go step by step:
- The
OutOfStockErrorclass is declared as a descendant ofException. - The warehouse has no tea left, so
stock.get(name, 0)gives0. - The
takefunction raisesOutOfStockError, and the linestock[name] -= 1does not run. - The
except OutOfStockErrorbranch catches the exception and printserror: no tea left.
A class with no body
The OutOfStockError class needs nothing but its parent, so its body is pass. It gets everything it needs from Exception:
- a message in parentheses when it is created;
- printing that message with
print; - working with
raiseandtry/except.
By convention, the name ends in Error: the name then shows that this is an exception.
The value of the class lies in the type itself: except OutOfStockError catches only this error and lets the others through. There is no need to compare the message text to learn the cause. This is the sign with exact wording.
Data in attributes
You can add data to an exception in attributes. A custom __init__ passes the message to the parent through super().__init__() and stores the rest in self:
class OutOfStockError(Exception):
def __init__(self, name):
super().__init__(f"no {name} left")
self.name = name
try:
raise OutOfStockError("tea")
except OutOfStockError as e:
print(e, "/", e.name) # no tea left / teaLet's see where each part comes from:
print(e)shows the message passed tosuper().__init__();e.nameis the attribute stored in__init__.
The calling code gets the item name ready to use and does not extract it from the text.
Catching by the parent
Custom exceptions can inherit from each other. Then an except with the parent class catches all its descendants, the way except Exception catches ValueError:
class ShopError(Exception):
pass
class OutOfStockError(ShopError):
pass
try:
raise OutOfStockError("no tea left")
except ShopError as e:
print("shop error:", e) # shop error: no tea leftThe calling code chooses how precise to be:
except OutOfStockErrorfor one case;except ShopErrorfor any warehouse error.
Here the sign picture ends: a real sign carries one wording, while an exception belongs to every class above it at once. The parent can also be another built-in class. An exception declared with class NegativeAmountError(ValueError): is also caught by an except ValueError branch written earlier.
Interviewers ask: "How do you create your own exception?" The answer:
- declare a class that descends from
Exception, notBaseException; - by convention, end its name in
Error; - if needed, add an
__init__with attributes, and raise it withraiselike a built-in one.
Common mistake
The class inherits from BaseException:
class StopError(BaseException): # ✗
pass
try:
raise StopError("stop")
except Exception:
print("caught")StopError: stopThe except Exception branch catches only descendants of Exception, and StopError is not one of them. The program stops with a traceback, as it does from Ctrl+C. Only orders to stop, such as KeyboardInterrupt, inherit from BaseException directly, and a warehouse error is not an order to stop. Inherit your exceptions from Exception.
Key points
- A custom exception is a class that descends from
Exception; its body is often onlypass, and its name ends inError. - A class of your own lets you catch only this error without parsing the message text.
- Data goes into attributes through a custom
__init__, and the message goes tosuper().__init__(). - An
exceptwith a parent class catches all descendants; custom exceptions inherit fromException, not fromBaseException.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.