[{"data":1,"prerenderedAt":698},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-custom-exceptions-explained":3},{"id":4,"title":5,"body":6,"description":684,"difficulty":685,"extension":686,"framework":687,"frameworkSlug":92,"meta":688,"navigation":130,"order":127,"path":689,"qaPath":690,"seo":691,"stem":692,"subtopic":693,"topic":694,"topicSlug":695,"updated":696,"__hash__":697},"blog\u002Fblog\u002Fpython-custom-exceptions-explained.md","Python Custom Exceptions Explained — The Exception Hierarchy and Designing Your Own Errors",{"type":7,"value":8,"toc":674},"minimark",[9,14,23,27,47,57,74,78,88,147,168,172,179,309,320,324,335,468,471,475,478,596,602,606,629,633,670],[10,11,13],"h2",{"id":12},"python-custom-exceptions-explained","Python custom exceptions, explained",[15,16,17,18,22],"p",{},"Python ships with a rich tree of built-in exceptions, and you can extend it with your own.\nUnderstanding the ",[19,20,21],"strong",{},"hierarchy"," tells you what to catch and what to subclass; designing good\ncustom exceptions makes your library's failures easy for callers to handle precisely.",[10,24,26],{"id":25},"the-built-in-hierarchy","The built-in hierarchy",[15,28,29,30,34,35,39,40,43,44,46],{},"Every exception descends from ",[31,32,33],"code",{},"BaseException",". Almost all ",[36,37,38],"em",{},"normal"," errors descend from\n",[31,41,42],{},"Exception"," — the level you should catch and subclass. A few system-level signals sit\nbeside it, deliberately outside ",[31,45,42],{},".",[48,49,54],"pre",{"className":50,"code":52,"language":53},[51],"language-text","BaseException\n ├── SystemExit\n ├── KeyboardInterrupt\n ├── GeneratorExit\n └── Exception\n      ├── ValueError, TypeError, KeyError, IndexError, ...\n      ├── ArithmeticError → ZeroDivisionError\n      └── OSError → FileNotFoundError, PermissionError, ...\n","text",[31,55,52],{"__ignoreMap":56},"",[15,58,59,60,62,63,65,66,69,70,73],{},"This is why you catch ",[31,61,42],{},", not ",[31,64,33],{},": catching the latter would also\nswallow ",[31,67,68],{},"KeyboardInterrupt"," (Ctrl-C) and ",[31,71,72],{},"SystemExit",", which you almost never want.",[10,75,77],{"id":76},"defining-a-custom-exception","Defining a custom exception",[15,79,80,81,83,84,87],{},"The minimum is a class that inherits from ",[31,82,42],{},". Often that empty body is enough — the\n",[19,85,86],{},"name"," carries the meaning.",[48,89,93],{"className":90,"code":91,"language":92,"meta":56,"style":56},"language-python shiki shiki-themes github-light github-dark","class ValidationError(Exception):\n    \"\"\"Raised when input fails validation.\"\"\"\n\nraise ValidationError(\"email is required\")\n","python",[31,94,95,118,125,132],{"__ignoreMap":56},[96,97,100,104,108,112,115],"span",{"class":98,"line":99},"line",1,[96,101,103],{"class":102},"szBVR","class",[96,105,107],{"class":106},"sScJk"," ValidationError",[96,109,111],{"class":110},"sVt8B","(",[96,113,42],{"class":114},"sj4cs",[96,116,117],{"class":110},"):\n",[96,119,121],{"class":98,"line":120},2,[96,122,124],{"class":123},"sZZnC","    \"\"\"Raised when input fails validation.\"\"\"\n",[96,126,128],{"class":98,"line":127},3,[96,129,131],{"emptyLinePlaceholder":130},true,"\n",[96,133,135,138,141,144],{"class":98,"line":134},4,[96,136,137],{"class":102},"raise",[96,139,140],{"class":110}," ValidationError(",[96,142,143],{"class":123},"\"email is required\"",[96,145,146],{"class":110},")\n",[15,148,149,150,152,153,156,157,160,161,163,164,167],{},"Inheriting from ",[31,151,42],{}," gives you message storage, ",[31,154,155],{},"str()",", and ",[31,158,159],{},"args"," for free. Avoid\nsubclassing ",[31,162,33],{}," directly — your errors should be catchable by ordinary\n",[31,165,166],{},"except Exception"," handlers.",[10,169,171],{"id":170},"a-base-class-per-package","A base class per package",[15,173,174,175,178],{},"A widely-used pattern: give your library one base exception, then derive specific ones from\nit. Callers can catch the base to handle ",[36,176,177],{},"anything"," from your library, or a specific\nsubclass for fine control.",[48,180,182],{"className":90,"code":181,"language":92,"meta":56,"style":56},"class PaymentError(Exception):\n    \"\"\"Base for all payment-related errors.\"\"\"\n\nclass CardDeclined(PaymentError):\n    pass\n\nclass InsufficientFunds(PaymentError):\n    pass\n\n# Caller chooses the granularity:\ntry:\n    charge(card)\nexcept InsufficientFunds:\n    top_up()\nexcept PaymentError:            # catches any other payment failure\n    log_and_alert()\n",[31,183,184,197,202,206,220,226,231,245,250,255,262,271,277,286,292,303],{"__ignoreMap":56},[96,185,186,188,191,193,195],{"class":98,"line":99},[96,187,103],{"class":102},[96,189,190],{"class":106}," PaymentError",[96,192,111],{"class":110},[96,194,42],{"class":114},[96,196,117],{"class":110},[96,198,199],{"class":98,"line":120},[96,200,201],{"class":123},"    \"\"\"Base for all payment-related errors.\"\"\"\n",[96,203,204],{"class":98,"line":127},[96,205,131],{"emptyLinePlaceholder":130},[96,207,208,210,213,215,218],{"class":98,"line":134},[96,209,103],{"class":102},[96,211,212],{"class":106}," CardDeclined",[96,214,111],{"class":110},[96,216,217],{"class":106},"PaymentError",[96,219,117],{"class":110},[96,221,223],{"class":98,"line":222},5,[96,224,225],{"class":102},"    pass\n",[96,227,229],{"class":98,"line":228},6,[96,230,131],{"emptyLinePlaceholder":130},[96,232,234,236,239,241,243],{"class":98,"line":233},7,[96,235,103],{"class":102},[96,237,238],{"class":106}," InsufficientFunds",[96,240,111],{"class":110},[96,242,217],{"class":106},[96,244,117],{"class":110},[96,246,248],{"class":98,"line":247},8,[96,249,225],{"class":102},[96,251,253],{"class":98,"line":252},9,[96,254,131],{"emptyLinePlaceholder":130},[96,256,258],{"class":98,"line":257},10,[96,259,261],{"class":260},"sJ8bj","# Caller chooses the granularity:\n",[96,263,265,268],{"class":98,"line":264},11,[96,266,267],{"class":102},"try",[96,269,270],{"class":110},":\n",[96,272,274],{"class":98,"line":273},12,[96,275,276],{"class":110},"    charge(card)\n",[96,278,280,283],{"class":98,"line":279},13,[96,281,282],{"class":102},"except",[96,284,285],{"class":110}," InsufficientFunds:\n",[96,287,289],{"class":98,"line":288},14,[96,290,291],{"class":110},"    top_up()\n",[96,293,295,297,300],{"class":98,"line":294},15,[96,296,282],{"class":102},[96,298,299],{"class":110}," PaymentError:            ",[96,301,302],{"class":260},"# catches any other payment failure\n",[96,304,306],{"class":98,"line":305},16,[96,307,308],{"class":110},"    log_and_alert()\n",[15,310,311,312,315,316,319],{},"This is the structure used by libraries like ",[31,313,314],{},"requests"," (",[31,317,318],{},"RequestException"," base) — it makes\nyour error surface predictable.",[10,321,323],{"id":322},"adding-useful-attributes","Adding useful attributes",[15,325,326,327,330,331,334],{},"Exceptions are just objects, so attach data callers need to react. Override ",[31,328,329],{},"__init__",", but\nremember to call ",[31,332,333],{},"super().__init__"," so the message still works.",[48,336,338],{"className":90,"code":337,"language":92,"meta":56,"style":56},"class APIError(Exception):\n    def __init__(self, message, *, status_code, response=None):\n        super().__init__(message)        # sets args\u002Fstr\n        self.status_code = status_code\n        self.response = response\n\ntry:\n    call_api()\nexcept APIError as e:\n    if e.status_code == 429:\n        backoff_and_retry()\n",[31,339,340,353,378,394,407,419,423,429,434,447,463],{"__ignoreMap":56},[96,341,342,344,347,349,351],{"class":98,"line":99},[96,343,103],{"class":102},[96,345,346],{"class":106}," APIError",[96,348,111],{"class":110},[96,350,42],{"class":114},[96,352,117],{"class":110},[96,354,355,358,361,364,367,370,373,376],{"class":98,"line":120},[96,356,357],{"class":102},"    def",[96,359,360],{"class":114}," __init__",[96,362,363],{"class":110},"(self, message, ",[96,365,366],{"class":102},"*",[96,368,369],{"class":110},", status_code, response",[96,371,372],{"class":102},"=",[96,374,375],{"class":114},"None",[96,377,117],{"class":110},[96,379,380,383,386,388,391],{"class":98,"line":127},[96,381,382],{"class":114},"        super",[96,384,385],{"class":110},"().",[96,387,329],{"class":114},[96,389,390],{"class":110},"(message)        ",[96,392,393],{"class":260},"# sets args\u002Fstr\n",[96,395,396,399,402,404],{"class":98,"line":134},[96,397,398],{"class":114},"        self",[96,400,401],{"class":110},".status_code ",[96,403,372],{"class":102},[96,405,406],{"class":110}," status_code\n",[96,408,409,411,414,416],{"class":98,"line":222},[96,410,398],{"class":114},[96,412,413],{"class":110},".response ",[96,415,372],{"class":102},[96,417,418],{"class":110}," response\n",[96,420,421],{"class":98,"line":228},[96,422,131],{"emptyLinePlaceholder":130},[96,424,425,427],{"class":98,"line":233},[96,426,267],{"class":102},[96,428,270],{"class":110},[96,430,431],{"class":98,"line":247},[96,432,433],{"class":110},"    call_api()\n",[96,435,436,438,441,444],{"class":98,"line":252},[96,437,282],{"class":102},[96,439,440],{"class":110}," APIError ",[96,442,443],{"class":102},"as",[96,445,446],{"class":110}," e:\n",[96,448,449,452,455,458,461],{"class":98,"line":257},[96,450,451],{"class":102},"    if",[96,453,454],{"class":110}," e.status_code ",[96,456,457],{"class":102},"==",[96,459,460],{"class":114}," 429",[96,462,270],{"class":110},[96,464,465],{"class":98,"line":264},[96,466,467],{"class":110},"        backoff_and_retry()\n",[15,469,470],{},"Structured attributes beat forcing callers to parse the message string.",[10,472,474],{"id":473},"raising-and-re-raising-well","Raising and re-raising well",[15,476,477],{},"Raise the most specific type you have, and when translating a lower-level error, chain it so\nthe original cause survives in the traceback.",[48,479,481],{"className":90,"code":480,"language":92,"meta":56,"style":56},"def load_user(uid):\n    try:\n        row = db.fetch(uid)\n    except db.DBError as e:\n        raise UserNotFound(f\"no user {uid}\") from e   # preserves the cause\n    if row is None:\n        raise UserNotFound(f\"no user {uid}\")\n",[31,482,483,494,501,511,523,561,576],{"__ignoreMap":56},[96,484,485,488,491],{"class":98,"line":99},[96,486,487],{"class":102},"def",[96,489,490],{"class":106}," load_user",[96,492,493],{"class":110},"(uid):\n",[96,495,496,499],{"class":98,"line":120},[96,497,498],{"class":102},"    try",[96,500,270],{"class":110},[96,502,503,506,508],{"class":98,"line":127},[96,504,505],{"class":110},"        row ",[96,507,372],{"class":102},[96,509,510],{"class":110}," db.fetch(uid)\n",[96,512,513,516,519,521],{"class":98,"line":134},[96,514,515],{"class":102},"    except",[96,517,518],{"class":110}," db.DBError ",[96,520,443],{"class":102},[96,522,446],{"class":110},[96,524,525,528,531,534,537,540,543,546,549,552,555,558],{"class":98,"line":222},[96,526,527],{"class":102},"        raise",[96,529,530],{"class":110}," UserNotFound(",[96,532,533],{"class":102},"f",[96,535,536],{"class":123},"\"no user ",[96,538,539],{"class":114},"{",[96,541,542],{"class":110},"uid",[96,544,545],{"class":114},"}",[96,547,548],{"class":123},"\"",[96,550,551],{"class":110},") ",[96,553,554],{"class":102},"from",[96,556,557],{"class":110}," e   ",[96,559,560],{"class":260},"# preserves the cause\n",[96,562,563,565,568,571,574],{"class":98,"line":228},[96,564,451],{"class":102},[96,566,567],{"class":110}," row ",[96,569,570],{"class":102},"is",[96,572,573],{"class":114}," None",[96,575,270],{"class":110},[96,577,578,580,582,584,586,588,590,592,594],{"class":98,"line":233},[96,579,527],{"class":102},[96,581,530],{"class":110},[96,583,533],{"class":102},[96,585,536],{"class":123},[96,587,539],{"class":114},[96,589,542],{"class":110},[96,591,545],{"class":114},[96,593,548],{"class":123},[96,595,146],{"class":110},[15,597,598,601],{},[31,599,600],{},"raise from"," keeps debugging information; swallowing the cause throws it away.",[10,603,605],{"id":604},"when-not-to-create-a-new-exception","When NOT to create a new exception",[15,607,608,609,612,613,616,617,620,621,624,625,628],{},"Don't invent a type when a built-in already says it precisely. Bad arguments → ",[31,610,611],{},"ValueError","\nor ",[31,614,615],{},"TypeError","; missing key → ",[31,618,619],{},"KeyError","; unsupported operation → ",[31,622,623],{},"NotImplementedError",".\nCustom exceptions earn their keep when callers need to ",[19,626,627],{},"catch your specific failure"," or\nwhen the error is a domain concept the standard library has no name for.",[10,630,632],{"id":631},"recap","Recap",[15,634,635,636,638,639,643,644,646,647,649,650,652,653,656,657,659,660,663,664,666,667,669],{},"All exceptions descend from ",[31,637,33],{},", but you catch and subclass ",[19,640,641],{},[31,642,42],{}," so\nyou don't swallow ",[31,645,68],{},"\u002F",[31,648,72],{},". Define custom errors by subclassing\n",[31,651,42],{}," — often just for the name — and give a library ",[19,654,655],{},"one base class"," with specific\nsubclasses so callers pick their granularity. Attach structured attributes via a\n",[31,658,333],{},"-calling constructor, chain translated errors with ",[31,661,662],{},"raise ... from",", and\nprefer a precise built-in (",[31,665,611],{},", ",[31,668,619],{},") when one already fits.",[671,672,673],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":56,"searchDepth":120,"depth":120,"links":675},[676,677,678,679,680,681,682,683],{"id":12,"depth":120,"text":13},{"id":25,"depth":120,"text":26},{"id":76,"depth":120,"text":77},{"id":170,"depth":120,"text":171},{"id":322,"depth":120,"text":323},{"id":473,"depth":120,"text":474},{"id":604,"depth":120,"text":605},{"id":631,"depth":120,"text":632},"How Python's exception hierarchy is structured and how to design custom exceptions — subclassing Exception, building an exception base class for your package, adding attributes, and when to create new types.","medium","md","Python",{},"\u002Fblog\u002Fpython-custom-exceptions-explained","\u002Fpython\u002Fexceptions\u002Fcustom-exceptions",{"title":5,"description":684},"blog\u002Fpython-custom-exceptions-explained","Custom Exceptions & the Hierarchy","Errors & Exceptions","exceptions","2026-06-19","C4OFzpF1lsdrJWIwL7pcGtaQPP3qw3rdl_fLivttucQ",1782244093454]