[{"data":1,"prerenderedAt":3571},["ShallowReactive",2],{"page-\u002Fpython\u002F16-error-handling":3},{"id":4,"title":5,"body":6,"description":27,"extension":3565,"meta":3566,"navigation":75,"path":3567,"seo":3568,"stem":3569,"__hash__":3570},"content\u002Fpython\u002F16-error-handling.md","16 — Error Handling",{"type":7,"value":8,"toc":3541},"minimark",[9,13,18,1204,1208,1345,1361,1433,1438,1490,1522,1529,1664,1678,1682,1934,1951,1958,2098,2113,2117,2187,2191,2502,2514,2528,2597,2615,2622,2696,2823,2842,2849,2910,2932,2936,3005,3009,3132,3136,3139,3296,3471,3475,3537],[10,11,5],"h1",{"id":12},"_16-error-handling",[14,15,17],"h2",{"id":16},"production-exception-hierarchy-structured-errors-for-api-boundaries","Production Exception Hierarchy — Structured Errors for API Boundaries",[19,20,22],"code-wrapper",{"language":21},"python",[23,24,28],"pre",{"className":25,"code":26,"language":21,"meta":27,"style":27},"language-python shiki shiki-themes github-light github-dark","# ── A production exception hierarchy for a service layer ──\n# Design goals:\n#   1. One base exception (ServiceError) for catch-all by callers\n#   2. Category exceptions (AuthError, ValidationError) for domain-specific handling\n#   3. Specific exceptions carry structured data for programmatic response building\n#   4. All exceptions chain from their cause via `raise ... from e`\n\nfrom typing import Any\n\nclass ServiceError(Exception):\n    \"\"\"Base for all service-layer errors. Catch this for 'anything from our code'.\"\"\"\n    def __init__(self, message: str, *, code: str = \"SERVICE_ERROR\"):\n        super().__init__(message)\n        self.code = code   # machine-readable error code for API responses\n\nclass ValidationError(ServiceError):\n    \"\"\"Input validation failure with per-field error details.\"\"\"\n    def __init__(self, errors: list[dict[str, str]]):\n        self.errors = errors   # [{\"field\": \"email\", \"message\": \"required\"}, ...]\n        message = \"; \".join(f\"{e['field']}: {e['message']}\" for e in errors)\n        super().__init__(message, code=\"VALIDATION_ERROR\")\n\nclass AuthError(ServiceError):\n    \"\"\"Authentication\u002Fauthorization failure.\"\"\"\n    pass\n\nclass RateLimitError(ServiceError):\n    \"\"\"Rate limit exceeded — carries retry-after hint.\"\"\"\n    def __init__(self, retry_after: float):\n        self.retry_after = retry_after\n        super().__init__(f\"rate limited, retry after {retry_after}s\", code=\"RATE_LIMITED\")\n\n# ── Usage: translating low-level errors into domain-specific ones ──\ndef parse_user(raw: dict[str, Any]) -> dict:\n    \"\"\"Parse and validate user input — raises ValidationError with field details.\"\"\"\n    errors = []\n\n    if not raw.get(\"email\"):\n        errors.append({\"field\": \"email\", \"message\": \"required\"})\n    elif \"@\" not in raw[\"email\"]:\n        errors.append({\"field\": \"email\", \"message\": \"invalid format\"})\n\n    age = raw.get(\"age\")\n    if age is None:\n        errors.append({\"field\": \"age\", \"message\": \"required\"})\n    elif not isinstance(age, int):\n        errors.append({\"field\": \"age\", \"message\": \"must be integer\"})\n    elif age \u003C 0 or age > 150:\n        errors.append({\"field\": \"age\", \"message\": \"must be 0-150\"})\n\n    if errors:\n        raise ValidationError(errors)   # structured data attached, not just a string\n\n    return raw\n\n# ── Exception chaining: translate a DB error into a domain error ──\nclass DatabaseError(ServiceError):\n    pass\n\ndef load_user(user_id: int) -> dict:\n    try:\n        # simulate a DB call that might fail\n        if user_id \u003C 0:\n            raise KeyError(f\"user {user_id} not found\")\n        return {\"id\": user_id, \"name\": \"Ada\"}\n    except KeyError as e:\n        # `from e` preserves the original traceback — debuggable, not just \"user not found\"\n        raise DatabaseError(f\"user {user_id} not found\") from e\n\n# ── API layer: converting exceptions to HTTP responses ──\ndef handle_request(raw_input: dict):\n    \"\"\"Simulated API handler — maps exceptions to structured HTTP-like responses.\"\"\"\n    try:\n        user = parse_user(raw_input)\n        return {\"status\": 200, \"body\": user}\n    except ValidationError as e:\n        return {\"status\": 422, \"body\": {\"code\": e.code, \"errors\": e.errors}}\n    except ServiceError as e:\n        return {\"status\": 500, \"body\": {\"code\": e.code, \"message\": str(e)}}\n\n# Test: validation failure with multiple field errors\nresult = handle_request({\"age\": -5})\nprint(result)\n# {'status': 422, 'body': {'code': 'VALIDATION_ERROR',\n#   'errors': [{'field': 'email', 'message': 'required'},\n#              {'field': 'age', 'message': 'must be 0-150'}]}}\n","",[29,30,31,40,46,52,58,64,70,77,94,99,119,126,160,175,193,198,213,219,238,254,317,340,345,359,365,371,376,390,396,411,424,461,466,472,495,501,512,517,534,560,582,604,609,624,640,661,679,701,727,749,754,762,774,779,788,793,799,813,818,823,843,851,857,872,900,926,940,946,974,979,985,1000,1006,1013,1029,1052,1065,1098,1110,1143,1148,1154,1177,1186,1192,1198],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"sdCPZ","# ── A production exception hierarchy for a service layer ──\n",[32,41,43],{"class":34,"line":42},2,[32,44,45],{"class":38},"# Design goals:\n",[32,47,49],{"class":34,"line":48},3,[32,50,51],{"class":38},"#   1. One base exception (ServiceError) for catch-all by callers\n",[32,53,55],{"class":34,"line":54},4,[32,56,57],{"class":38},"#   2. Category exceptions (AuthError, ValidationError) for domain-specific handling\n",[32,59,61],{"class":34,"line":60},5,[32,62,63],{"class":38},"#   3. Specific exceptions carry structured data for programmatic response building\n",[32,65,67],{"class":34,"line":66},6,[32,68,69],{"class":38},"#   4. All exceptions chain from their cause via `raise ... from e`\n",[32,71,73],{"class":34,"line":72},7,[32,74,76],{"emptyLinePlaceholder":75},true,"\n",[32,78,80,84,88,91],{"class":34,"line":79},8,[32,81,83],{"class":82},"svdQ7","from",[32,85,87],{"class":86},"ssxIu"," typing ",[32,89,90],{"class":82},"import",[32,92,93],{"class":86}," Any\n",[32,95,97],{"class":34,"line":96},9,[32,98,76],{"emptyLinePlaceholder":75},[32,100,102,105,109,112,116],{"class":34,"line":101},10,[32,103,104],{"class":82},"class",[32,106,108],{"class":107},"sIsaT"," ServiceError",[32,110,111],{"class":86},"(",[32,113,115],{"class":114},"snvgF","Exception",[32,117,118],{"class":86},"):\n",[32,120,122],{"class":34,"line":121},11,[32,123,125],{"class":124},"sJ6F3","    \"\"\"Base for all service-layer errors. Catch this for 'anything from our code'.\"\"\"\n",[32,127,129,132,135,138,141,144,147,150,152,155,158],{"class":34,"line":128},12,[32,130,131],{"class":82},"    def",[32,133,134],{"class":114}," __init__",[32,136,137],{"class":86},"(self, message: ",[32,139,140],{"class":114},"str",[32,142,143],{"class":86},", ",[32,145,146],{"class":82},"*",[32,148,149],{"class":86},", code: ",[32,151,140],{"class":114},[32,153,154],{"class":82}," =",[32,156,157],{"class":124}," \"SERVICE_ERROR\"",[32,159,118],{"class":86},[32,161,163,166,169,172],{"class":34,"line":162},13,[32,164,165],{"class":114},"        super",[32,167,168],{"class":86},"().",[32,170,171],{"class":114},"__init__",[32,173,174],{"class":86},"(message)\n",[32,176,178,181,184,187,190],{"class":34,"line":177},14,[32,179,180],{"class":114},"        self",[32,182,183],{"class":86},".code ",[32,185,186],{"class":82},"=",[32,188,189],{"class":86}," code   ",[32,191,192],{"class":38},"# machine-readable error code for API responses\n",[32,194,196],{"class":34,"line":195},15,[32,197,76],{"emptyLinePlaceholder":75},[32,199,201,203,206,208,211],{"class":34,"line":200},16,[32,202,104],{"class":82},[32,204,205],{"class":107}," ValidationError",[32,207,111],{"class":86},[32,209,210],{"class":107},"ServiceError",[32,212,118],{"class":86},[32,214,216],{"class":34,"line":215},17,[32,217,218],{"class":124},"    \"\"\"Input validation failure with per-field error details.\"\"\"\n",[32,220,222,224,226,229,231,233,235],{"class":34,"line":221},18,[32,223,131],{"class":82},[32,225,134],{"class":114},[32,227,228],{"class":86},"(self, errors: list[dict[",[32,230,140],{"class":114},[32,232,143],{"class":86},[32,234,140],{"class":114},[32,236,237],{"class":86},"]]):\n",[32,239,241,243,246,248,251],{"class":34,"line":240},19,[32,242,180],{"class":114},[32,244,245],{"class":86},".errors ",[32,247,186],{"class":82},[32,249,250],{"class":86}," errors   ",[32,252,253],{"class":38},"# [{\"field\": \"email\", \"message\": \"required\"}, ...]\n",[32,255,257,260,262,265,268,271,274,277,280,283,286,289,292,294,296,299,301,303,305,308,311,314],{"class":34,"line":256},20,[32,258,259],{"class":86},"        message ",[32,261,186],{"class":82},[32,263,264],{"class":124}," \"; \"",[32,266,267],{"class":86},".join(",[32,269,270],{"class":82},"f",[32,272,273],{"class":124},"\"",[32,275,276],{"class":114},"{",[32,278,279],{"class":86},"e[",[32,281,282],{"class":124},"'field'",[32,284,285],{"class":86},"]",[32,287,288],{"class":114},"}",[32,290,291],{"class":124},": ",[32,293,276],{"class":114},[32,295,279],{"class":86},[32,297,298],{"class":124},"'message'",[32,300,285],{"class":86},[32,302,288],{"class":114},[32,304,273],{"class":124},[32,306,307],{"class":82}," for",[32,309,310],{"class":86}," e ",[32,312,313],{"class":82},"in",[32,315,316],{"class":86}," errors)\n",[32,318,320,322,324,326,329,332,334,337],{"class":34,"line":319},21,[32,321,165],{"class":114},[32,323,168],{"class":86},[32,325,171],{"class":114},[32,327,328],{"class":86},"(message, ",[32,330,29],{"class":331},"sCrzJ",[32,333,186],{"class":82},[32,335,336],{"class":124},"\"VALIDATION_ERROR\"",[32,338,339],{"class":86},")\n",[32,341,343],{"class":34,"line":342},22,[32,344,76],{"emptyLinePlaceholder":75},[32,346,348,350,353,355,357],{"class":34,"line":347},23,[32,349,104],{"class":82},[32,351,352],{"class":107}," AuthError",[32,354,111],{"class":86},[32,356,210],{"class":107},[32,358,118],{"class":86},[32,360,362],{"class":34,"line":361},24,[32,363,364],{"class":124},"    \"\"\"Authentication\u002Fauthorization failure.\"\"\"\n",[32,366,368],{"class":34,"line":367},25,[32,369,370],{"class":82},"    pass\n",[32,372,374],{"class":34,"line":373},26,[32,375,76],{"emptyLinePlaceholder":75},[32,377,379,381,384,386,388],{"class":34,"line":378},27,[32,380,104],{"class":82},[32,382,383],{"class":107}," RateLimitError",[32,385,111],{"class":86},[32,387,210],{"class":107},[32,389,118],{"class":86},[32,391,393],{"class":34,"line":392},28,[32,394,395],{"class":124},"    \"\"\"Rate limit exceeded — carries retry-after hint.\"\"\"\n",[32,397,399,401,403,406,409],{"class":34,"line":398},29,[32,400,131],{"class":82},[32,402,134],{"class":114},[32,404,405],{"class":86},"(self, retry_after: ",[32,407,408],{"class":114},"float",[32,410,118],{"class":86},[32,412,414,416,419,421],{"class":34,"line":413},30,[32,415,180],{"class":114},[32,417,418],{"class":86},".retry_after ",[32,420,186],{"class":82},[32,422,423],{"class":86}," retry_after\n",[32,425,427,429,431,433,435,437,440,442,445,447,450,452,454,456,459],{"class":34,"line":426},31,[32,428,165],{"class":114},[32,430,168],{"class":86},[32,432,171],{"class":114},[32,434,111],{"class":86},[32,436,270],{"class":82},[32,438,439],{"class":124},"\"rate limited, retry after ",[32,441,276],{"class":114},[32,443,444],{"class":86},"retry_after",[32,446,288],{"class":114},[32,448,449],{"class":124},"s\"",[32,451,143],{"class":86},[32,453,29],{"class":331},[32,455,186],{"class":82},[32,457,458],{"class":124},"\"RATE_LIMITED\"",[32,460,339],{"class":86},[32,462,464],{"class":34,"line":463},32,[32,465,76],{"emptyLinePlaceholder":75},[32,467,469],{"class":34,"line":468},33,[32,470,471],{"class":38},"# ── Usage: translating low-level errors into domain-specific ones ──\n",[32,473,475,478,481,484,486,489,492],{"class":34,"line":474},34,[32,476,477],{"class":82},"def",[32,479,480],{"class":107}," parse_user",[32,482,483],{"class":86},"(raw: dict[",[32,485,140],{"class":114},[32,487,488],{"class":86},", Any]) -> ",[32,490,491],{"class":114},"dict",[32,493,494],{"class":86},":\n",[32,496,498],{"class":34,"line":497},35,[32,499,500],{"class":124},"    \"\"\"Parse and validate user input — raises ValidationError with field details.\"\"\"\n",[32,502,504,507,509],{"class":34,"line":503},36,[32,505,506],{"class":86},"    errors ",[32,508,186],{"class":82},[32,510,511],{"class":86}," []\n",[32,513,515],{"class":34,"line":514},37,[32,516,76],{"emptyLinePlaceholder":75},[32,518,520,523,526,529,532],{"class":34,"line":519},38,[32,521,522],{"class":82},"    if",[32,524,525],{"class":82}," not",[32,527,528],{"class":86}," raw.get(",[32,530,531],{"class":124},"\"email\"",[32,533,118],{"class":86},[32,535,537,540,543,545,547,549,552,554,557],{"class":34,"line":536},39,[32,538,539],{"class":86},"        errors.append({",[32,541,542],{"class":124},"\"field\"",[32,544,291],{"class":86},[32,546,531],{"class":124},[32,548,143],{"class":86},[32,550,551],{"class":124},"\"message\"",[32,553,291],{"class":86},[32,555,556],{"class":124},"\"required\"",[32,558,559],{"class":86},"})\n",[32,561,563,566,569,571,574,577,579],{"class":34,"line":562},40,[32,564,565],{"class":82},"    elif",[32,567,568],{"class":124}," \"@\"",[32,570,525],{"class":82},[32,572,573],{"class":82}," in",[32,575,576],{"class":86}," raw[",[32,578,531],{"class":124},[32,580,581],{"class":86},"]:\n",[32,583,585,587,589,591,593,595,597,599,602],{"class":34,"line":584},41,[32,586,539],{"class":86},[32,588,542],{"class":124},[32,590,291],{"class":86},[32,592,531],{"class":124},[32,594,143],{"class":86},[32,596,551],{"class":124},[32,598,291],{"class":86},[32,600,601],{"class":124},"\"invalid format\"",[32,603,559],{"class":86},[32,605,607],{"class":34,"line":606},42,[32,608,76],{"emptyLinePlaceholder":75},[32,610,612,615,617,619,622],{"class":34,"line":611},43,[32,613,614],{"class":86},"    age ",[32,616,186],{"class":82},[32,618,528],{"class":86},[32,620,621],{"class":124},"\"age\"",[32,623,339],{"class":86},[32,625,627,629,632,635,638],{"class":34,"line":626},44,[32,628,522],{"class":82},[32,630,631],{"class":86}," age ",[32,633,634],{"class":82},"is",[32,636,637],{"class":114}," None",[32,639,494],{"class":86},[32,641,643,645,647,649,651,653,655,657,659],{"class":34,"line":642},45,[32,644,539],{"class":86},[32,646,542],{"class":124},[32,648,291],{"class":86},[32,650,621],{"class":124},[32,652,143],{"class":86},[32,654,551],{"class":124},[32,656,291],{"class":86},[32,658,556],{"class":124},[32,660,559],{"class":86},[32,662,664,666,668,671,674,677],{"class":34,"line":663},46,[32,665,565],{"class":82},[32,667,525],{"class":82},[32,669,670],{"class":114}," isinstance",[32,672,673],{"class":86},"(age, ",[32,675,676],{"class":114},"int",[32,678,118],{"class":86},[32,680,682,684,686,688,690,692,694,696,699],{"class":34,"line":681},47,[32,683,539],{"class":86},[32,685,542],{"class":124},[32,687,291],{"class":86},[32,689,621],{"class":124},[32,691,143],{"class":86},[32,693,551],{"class":124},[32,695,291],{"class":86},[32,697,698],{"class":124},"\"must be integer\"",[32,700,559],{"class":86},[32,702,704,706,708,711,714,717,719,722,725],{"class":34,"line":703},48,[32,705,565],{"class":82},[32,707,631],{"class":86},[32,709,710],{"class":82},"\u003C",[32,712,713],{"class":114}," 0",[32,715,716],{"class":82}," or",[32,718,631],{"class":86},[32,720,721],{"class":82},">",[32,723,724],{"class":114}," 150",[32,726,494],{"class":86},[32,728,730,732,734,736,738,740,742,744,747],{"class":34,"line":729},49,[32,731,539],{"class":86},[32,733,542],{"class":124},[32,735,291],{"class":86},[32,737,621],{"class":124},[32,739,143],{"class":86},[32,741,551],{"class":124},[32,743,291],{"class":86},[32,745,746],{"class":124},"\"must be 0-150\"",[32,748,559],{"class":86},[32,750,752],{"class":34,"line":751},50,[32,753,76],{"emptyLinePlaceholder":75},[32,755,757,759],{"class":34,"line":756},51,[32,758,522],{"class":82},[32,760,761],{"class":86}," errors:\n",[32,763,765,768,771],{"class":34,"line":764},52,[32,766,767],{"class":82},"        raise",[32,769,770],{"class":86}," ValidationError(errors)   ",[32,772,773],{"class":38},"# structured data attached, not just a string\n",[32,775,777],{"class":34,"line":776},53,[32,778,76],{"emptyLinePlaceholder":75},[32,780,782,785],{"class":34,"line":781},54,[32,783,784],{"class":82},"    return",[32,786,787],{"class":86}," raw\n",[32,789,791],{"class":34,"line":790},55,[32,792,76],{"emptyLinePlaceholder":75},[32,794,796],{"class":34,"line":795},56,[32,797,798],{"class":38},"# ── Exception chaining: translate a DB error into a domain error ──\n",[32,800,802,804,807,809,811],{"class":34,"line":801},57,[32,803,104],{"class":82},[32,805,806],{"class":107}," DatabaseError",[32,808,111],{"class":86},[32,810,210],{"class":107},[32,812,118],{"class":86},[32,814,816],{"class":34,"line":815},58,[32,817,370],{"class":82},[32,819,821],{"class":34,"line":820},59,[32,822,76],{"emptyLinePlaceholder":75},[32,824,826,828,831,834,836,839,841],{"class":34,"line":825},60,[32,827,477],{"class":82},[32,829,830],{"class":107}," load_user",[32,832,833],{"class":86},"(user_id: ",[32,835,676],{"class":114},[32,837,838],{"class":86},") -> ",[32,840,491],{"class":114},[32,842,494],{"class":86},[32,844,846,849],{"class":34,"line":845},61,[32,847,848],{"class":82},"    try",[32,850,494],{"class":86},[32,852,854],{"class":34,"line":853},62,[32,855,856],{"class":38},"        # simulate a DB call that might fail\n",[32,858,860,863,866,868,870],{"class":34,"line":859},63,[32,861,862],{"class":82},"        if",[32,864,865],{"class":86}," user_id ",[32,867,710],{"class":82},[32,869,713],{"class":114},[32,871,494],{"class":86},[32,873,875,878,881,883,885,888,890,893,895,898],{"class":34,"line":874},64,[32,876,877],{"class":82},"            raise",[32,879,880],{"class":114}," KeyError",[32,882,111],{"class":86},[32,884,270],{"class":82},[32,886,887],{"class":124},"\"user ",[32,889,276],{"class":114},[32,891,892],{"class":86},"user_id",[32,894,288],{"class":114},[32,896,897],{"class":124}," not found\"",[32,899,339],{"class":86},[32,901,903,906,909,912,915,918,920,923],{"class":34,"line":902},65,[32,904,905],{"class":82},"        return",[32,907,908],{"class":86}," {",[32,910,911],{"class":124},"\"id\"",[32,913,914],{"class":86},": user_id, ",[32,916,917],{"class":124},"\"name\"",[32,919,291],{"class":86},[32,921,922],{"class":124},"\"Ada\"",[32,924,925],{"class":86},"}\n",[32,927,929,932,934,937],{"class":34,"line":928},66,[32,930,931],{"class":82},"    except",[32,933,880],{"class":114},[32,935,936],{"class":82}," as",[32,938,939],{"class":86}," e:\n",[32,941,943],{"class":34,"line":942},67,[32,944,945],{"class":38},"        # `from e` preserves the original traceback — debuggable, not just \"user not found\"\n",[32,947,949,951,954,956,958,960,962,964,966,969,971],{"class":34,"line":948},68,[32,950,767],{"class":82},[32,952,953],{"class":86}," DatabaseError(",[32,955,270],{"class":82},[32,957,887],{"class":124},[32,959,276],{"class":114},[32,961,892],{"class":86},[32,963,288],{"class":114},[32,965,897],{"class":124},[32,967,968],{"class":86},") ",[32,970,83],{"class":82},[32,972,973],{"class":86}," e\n",[32,975,977],{"class":34,"line":976},69,[32,978,76],{"emptyLinePlaceholder":75},[32,980,982],{"class":34,"line":981},70,[32,983,984],{"class":38},"# ── API layer: converting exceptions to HTTP responses ──\n",[32,986,988,990,993,996,998],{"class":34,"line":987},71,[32,989,477],{"class":82},[32,991,992],{"class":107}," handle_request",[32,994,995],{"class":86},"(raw_input: ",[32,997,491],{"class":114},[32,999,118],{"class":86},[32,1001,1003],{"class":34,"line":1002},72,[32,1004,1005],{"class":124},"    \"\"\"Simulated API handler — maps exceptions to structured HTTP-like responses.\"\"\"\n",[32,1007,1009,1011],{"class":34,"line":1008},73,[32,1010,848],{"class":82},[32,1012,494],{"class":86},[32,1014,1016,1019,1021,1024,1027],{"class":34,"line":1015},74,[32,1017,1018],{"class":86},"        user ",[32,1020,186],{"class":82},[32,1022,1023],{"class":86}," parse_user(",[32,1025,1026],{"class":331},"raw_input",[32,1028,339],{"class":86},[32,1030,1032,1034,1036,1039,1041,1044,1046,1049],{"class":34,"line":1031},75,[32,1033,905],{"class":82},[32,1035,908],{"class":86},[32,1037,1038],{"class":124},"\"status\"",[32,1040,291],{"class":86},[32,1042,1043],{"class":114},"200",[32,1045,143],{"class":86},[32,1047,1048],{"class":124},"\"body\"",[32,1050,1051],{"class":86},": user}\n",[32,1053,1055,1057,1060,1063],{"class":34,"line":1054},76,[32,1056,931],{"class":82},[32,1058,1059],{"class":86}," ValidationError ",[32,1061,1062],{"class":82},"as",[32,1064,939],{"class":86},[32,1066,1068,1070,1072,1074,1076,1079,1081,1083,1086,1089,1092,1095],{"class":34,"line":1067},77,[32,1069,905],{"class":82},[32,1071,908],{"class":86},[32,1073,1038],{"class":124},[32,1075,291],{"class":86},[32,1077,1078],{"class":114},"422",[32,1080,143],{"class":86},[32,1082,1048],{"class":124},[32,1084,1085],{"class":86},": {",[32,1087,1088],{"class":124},"\"code\"",[32,1090,1091],{"class":86},": e.code, ",[32,1093,1094],{"class":124},"\"errors\"",[32,1096,1097],{"class":86},": e.errors}}\n",[32,1099,1101,1103,1106,1108],{"class":34,"line":1100},78,[32,1102,931],{"class":82},[32,1104,1105],{"class":86}," ServiceError ",[32,1107,1062],{"class":82},[32,1109,939],{"class":86},[32,1111,1113,1115,1117,1119,1121,1124,1126,1128,1130,1132,1134,1136,1138,1140],{"class":34,"line":1112},79,[32,1114,905],{"class":82},[32,1116,908],{"class":86},[32,1118,1038],{"class":124},[32,1120,291],{"class":86},[32,1122,1123],{"class":114},"500",[32,1125,143],{"class":86},[32,1127,1048],{"class":124},[32,1129,1085],{"class":86},[32,1131,1088],{"class":124},[32,1133,1091],{"class":86},[32,1135,551],{"class":124},[32,1137,291],{"class":86},[32,1139,140],{"class":114},[32,1141,1142],{"class":86},"(e)}}\n",[32,1144,1146],{"class":34,"line":1145},80,[32,1147,76],{"emptyLinePlaceholder":75},[32,1149,1151],{"class":34,"line":1150},81,[32,1152,1153],{"class":38},"# Test: validation failure with multiple field errors\n",[32,1155,1157,1160,1162,1165,1167,1169,1172,1175],{"class":34,"line":1156},82,[32,1158,1159],{"class":86},"result ",[32,1161,186],{"class":82},[32,1163,1164],{"class":86}," handle_request({",[32,1166,621],{"class":124},[32,1168,291],{"class":86},[32,1170,1171],{"class":82},"-",[32,1173,1174],{"class":114},"5",[32,1176,559],{"class":86},[32,1178,1180,1183],{"class":34,"line":1179},83,[32,1181,1182],{"class":114},"print",[32,1184,1185],{"class":86},"(result)\n",[32,1187,1189],{"class":34,"line":1188},84,[32,1190,1191],{"class":38},"# {'status': 422, 'body': {'code': 'VALIDATION_ERROR',\n",[32,1193,1195],{"class":34,"line":1194},85,[32,1196,1197],{"class":38},"#   'errors': [{'field': 'email', 'message': 'required'},\n",[32,1199,1201],{"class":34,"line":1200},86,[32,1202,1203],{"class":38},"#              {'field': 'age', 'message': 'must be 0-150'}]}}\n",[14,1205,1207],{"id":1206},"exception-hierarchy-and-catching-specifically","Exception Hierarchy and Catching Specifically",[19,1209,1210],{"language":21},[23,1211,1213],{"className":25,"code":1212,"language":21,"meta":27,"style":27},"try:\n    result = 10 \u002F 0\nexcept ZeroDivisionError as e:\n    print(f\"Math error: {e}\")\nexcept ArithmeticError as e:          # ZeroDivisionError IS-A ArithmeticError — order matters!\n    print(f\"Other arithmetic error: {e}\")\nexcept Exception as e:                  # broad catch-all — catches almost everything else\n    print(f\"Unexpected: {e}\")\n",[29,1214,1215,1222,1238,1250,1273,1288,1309,1324],{"__ignoreMap":27},[32,1216,1217,1220],{"class":34,"line":35},[32,1218,1219],{"class":82},"try",[32,1221,494],{"class":86},[32,1223,1224,1227,1229,1232,1235],{"class":34,"line":42},[32,1225,1226],{"class":86},"    result ",[32,1228,186],{"class":82},[32,1230,1231],{"class":114}," 10",[32,1233,1234],{"class":82}," \u002F",[32,1236,1237],{"class":114}," 0\n",[32,1239,1240,1243,1246,1248],{"class":34,"line":48},[32,1241,1242],{"class":82},"except",[32,1244,1245],{"class":114}," ZeroDivisionError",[32,1247,936],{"class":82},[32,1249,939],{"class":86},[32,1251,1252,1255,1257,1259,1262,1264,1267,1269,1271],{"class":34,"line":54},[32,1253,1254],{"class":114},"    print",[32,1256,111],{"class":86},[32,1258,270],{"class":82},[32,1260,1261],{"class":124},"\"Math error: ",[32,1263,276],{"class":114},[32,1265,1266],{"class":86},"e",[32,1268,288],{"class":114},[32,1270,273],{"class":124},[32,1272,339],{"class":86},[32,1274,1275,1277,1280,1282,1285],{"class":34,"line":60},[32,1276,1242],{"class":82},[32,1278,1279],{"class":114}," ArithmeticError",[32,1281,936],{"class":82},[32,1283,1284],{"class":86}," e:          ",[32,1286,1287],{"class":38},"# ZeroDivisionError IS-A ArithmeticError — order matters!\n",[32,1289,1290,1292,1294,1296,1299,1301,1303,1305,1307],{"class":34,"line":66},[32,1291,1254],{"class":114},[32,1293,111],{"class":86},[32,1295,270],{"class":82},[32,1297,1298],{"class":124},"\"Other arithmetic error: ",[32,1300,276],{"class":114},[32,1302,1266],{"class":86},[32,1304,288],{"class":114},[32,1306,273],{"class":124},[32,1308,339],{"class":86},[32,1310,1311,1313,1316,1318,1321],{"class":34,"line":72},[32,1312,1242],{"class":82},[32,1314,1315],{"class":114}," Exception",[32,1317,936],{"class":82},[32,1319,1320],{"class":86}," e:                  ",[32,1322,1323],{"class":38},"# broad catch-all — catches almost everything else\n",[32,1325,1326,1328,1330,1332,1335,1337,1339,1341,1343],{"class":34,"line":79},[32,1327,1254],{"class":114},[32,1329,111],{"class":86},[32,1331,270],{"class":82},[32,1333,1334],{"class":124},"\"Unexpected: ",[32,1336,276],{"class":114},[32,1338,1266],{"class":86},[32,1340,288],{"class":114},[32,1342,273],{"class":124},[32,1344,339],{"class":86},[1346,1347,1348,1349,1351,1352,1356,1357,1360],"p",{},"Python checks ",[29,1350,1242],{}," clauses ",[1353,1354,1355],"strong",{},"top-to-bottom",", using the first one whose type matches (via ",[29,1358,1359],{},"isinstance",", so subclasses match parent-type clauses) — placing a broader exception type before a narrower one makes the narrower clause unreachable dead code, a mistake Python doesn't warn about by default.",[19,1362,1363],{"language":21},[23,1364,1366],{"className":25,"code":1365,"language":21,"meta":27,"style":27},"# WRONG — the ArithmeticError clause makes ZeroDivisionError's clause unreachable\ntry:\n    1 \u002F 0\nexcept ArithmeticError:\n    print(\"caught as ArithmeticError\")    # this always wins; ZeroDivisionError never gets a chance\nexcept ZeroDivisionError:\n    print(\"caught as ZeroDivisionError\")    # DEAD CODE — never runs\n",[29,1367,1368,1373,1379,1388,1396,1411,1419],{"__ignoreMap":27},[32,1369,1370],{"class":34,"line":35},[32,1371,1372],{"class":38},"# WRONG — the ArithmeticError clause makes ZeroDivisionError's clause unreachable\n",[32,1374,1375,1377],{"class":34,"line":42},[32,1376,1219],{"class":82},[32,1378,494],{"class":86},[32,1380,1381,1384,1386],{"class":34,"line":48},[32,1382,1383],{"class":114},"    1",[32,1385,1234],{"class":82},[32,1387,1237],{"class":114},[32,1389,1390,1392,1394],{"class":34,"line":54},[32,1391,1242],{"class":82},[32,1393,1279],{"class":114},[32,1395,494],{"class":86},[32,1397,1398,1400,1402,1405,1408],{"class":34,"line":60},[32,1399,1254],{"class":114},[32,1401,111],{"class":86},[32,1403,1404],{"class":124},"\"caught as ArithmeticError\"",[32,1406,1407],{"class":86},")    ",[32,1409,1410],{"class":38},"# this always wins; ZeroDivisionError never gets a chance\n",[32,1412,1413,1415,1417],{"class":34,"line":66},[32,1414,1242],{"class":82},[32,1416,1245],{"class":114},[32,1418,494],{"class":86},[32,1420,1421,1423,1425,1428,1430],{"class":34,"line":72},[32,1422,1254],{"class":114},[32,1424,111],{"class":86},[32,1426,1427],{"class":124},"\"caught as ZeroDivisionError\"",[32,1429,1407],{"class":86},[32,1431,1432],{"class":38},"# DEAD CODE — never runs\n",[1434,1435,1437],"h3",{"id":1436},"the-exception-hierarchy-that-matters-day-to-day","The exception hierarchy that matters day-to-day",[19,1439,1440],{"language":21},[23,1441,1443],{"className":25,"code":1442,"language":21,"meta":27,"style":27},"# BaseException\n#  +-- SystemExit, KeyboardInterrupt, GeneratorExit   (deliberately NOT caught by `except Exception`)\n#  +-- Exception\n#       +-- ArithmeticError -> ZeroDivisionError, OverflowError\n#       +-- LookupError -> IndexError, KeyError\n#       +-- ValueError\n#       +-- TypeError\n#       +-- OSError -> FileNotFoundError, PermissionError, ConnectionError, TimeoutError\n#       +-- RuntimeError -> RecursionError, NotImplementedError\n",[29,1444,1445,1450,1455,1460,1465,1470,1475,1480,1485],{"__ignoreMap":27},[32,1446,1447],{"class":34,"line":35},[32,1448,1449],{"class":38},"# BaseException\n",[32,1451,1452],{"class":34,"line":42},[32,1453,1454],{"class":38},"#  +-- SystemExit, KeyboardInterrupt, GeneratorExit   (deliberately NOT caught by `except Exception`)\n",[32,1456,1457],{"class":34,"line":48},[32,1458,1459],{"class":38},"#  +-- Exception\n",[32,1461,1462],{"class":34,"line":54},[32,1463,1464],{"class":38},"#       +-- ArithmeticError -> ZeroDivisionError, OverflowError\n",[32,1466,1467],{"class":34,"line":60},[32,1468,1469],{"class":38},"#       +-- LookupError -> IndexError, KeyError\n",[32,1471,1472],{"class":34,"line":66},[32,1473,1474],{"class":38},"#       +-- ValueError\n",[32,1476,1477],{"class":34,"line":72},[32,1478,1479],{"class":38},"#       +-- TypeError\n",[32,1481,1482],{"class":34,"line":79},[32,1483,1484],{"class":38},"#       +-- OSError -> FileNotFoundError, PermissionError, ConnectionError, TimeoutError\n",[32,1486,1487],{"class":34,"line":96},[32,1488,1489],{"class":38},"#       +-- RuntimeError -> RecursionError, NotImplementedError\n",[1346,1491,1492,1495,1496,1499,1500,1503,1504,1507,1508,1511,1512,1514,1515,1518,1519,1521],{},[29,1493,1494],{},"except Exception"," deliberately does ",[1353,1497,1498],{},"not"," catch ",[29,1501,1502],{},"SystemExit"," or ",[29,1505,1506],{},"KeyboardInterrupt"," (both inherit directly from ",[29,1509,1510],{},"BaseException",", not ",[29,1513,115],{},") — this is intentional, so that ",[29,1516,1517],{},"sys.exit()"," and Ctrl+C reliably terminate a program even inside broad exception-handling code, unless a handler explicitly asks to catch ",[29,1520,1510],{},".",[14,1523,1525,1526],{"id":1524},"never-use-a-bare-except","Never Use a Bare ",[29,1527,1528],{},"except:",[19,1530,1531],{"language":21},[23,1532,1534],{"className":25,"code":1533,"language":21,"meta":27,"style":27},"# DANGEROUS — catches EVERYTHING, including KeyboardInterrupt, SystemExit, and typos manifesting as NameError\ndef risky():\n    try:\n        return complicated_calculation()   # NameError: name not defined — a genuine BUG\n    except:\n        return None                          # silently hides the bug, returns None instead\n\n# BETTER — catch only what you can actually handle meaningfully\ndef risky_fixed():\n    try:\n        return complicated_calculation()\n    except (ValueError, ZeroDivisionError) as e:\n        log.warning(f\"Calculation failed: {e}\")\n        return None\n",[29,1535,1536,1541,1551,1557,1570,1576,1585,1589,1594,1603,1609,1616,1637,1657],{"__ignoreMap":27},[32,1537,1538],{"class":34,"line":35},[32,1539,1540],{"class":38},"# DANGEROUS — catches EVERYTHING, including KeyboardInterrupt, SystemExit, and typos manifesting as NameError\n",[32,1542,1543,1545,1548],{"class":34,"line":42},[32,1544,477],{"class":82},[32,1546,1547],{"class":107}," risky",[32,1549,1550],{"class":86},"():\n",[32,1552,1553,1555],{"class":34,"line":48},[32,1554,848],{"class":82},[32,1556,494],{"class":86},[32,1558,1559,1561,1564,1567],{"class":34,"line":54},[32,1560,905],{"class":82},[32,1562,1563],{"class":86}," complicated_calculation()   ",[32,1565,1566],{"class":38},"# NameError: name not defined — a genuine ",[32,1568,1569],{"class":82},"BUG\n",[32,1571,1572,1574],{"class":34,"line":60},[32,1573,931],{"class":82},[32,1575,494],{"class":86},[32,1577,1578,1580,1582],{"class":34,"line":66},[32,1579,905],{"class":82},[32,1581,637],{"class":114},[32,1583,1584],{"class":38},"                          # silently hides the bug, returns None instead\n",[32,1586,1587],{"class":34,"line":72},[32,1588,76],{"emptyLinePlaceholder":75},[32,1590,1591],{"class":34,"line":79},[32,1592,1593],{"class":38},"# BETTER — catch only what you can actually handle meaningfully\n",[32,1595,1596,1598,1601],{"class":34,"line":96},[32,1597,477],{"class":82},[32,1599,1600],{"class":107}," risky_fixed",[32,1602,1550],{"class":86},[32,1604,1605,1607],{"class":34,"line":101},[32,1606,848],{"class":82},[32,1608,494],{"class":86},[32,1610,1611,1613],{"class":34,"line":121},[32,1612,905],{"class":82},[32,1614,1615],{"class":86}," complicated_calculation()\n",[32,1617,1618,1620,1623,1626,1628,1631,1633,1635],{"class":34,"line":128},[32,1619,931],{"class":82},[32,1621,1622],{"class":86}," (",[32,1624,1625],{"class":114},"ValueError",[32,1627,143],{"class":86},[32,1629,1630],{"class":114},"ZeroDivisionError",[32,1632,968],{"class":86},[32,1634,1062],{"class":82},[32,1636,939],{"class":86},[32,1638,1639,1642,1644,1647,1649,1651,1653,1655],{"class":34,"line":162},[32,1640,1641],{"class":86},"        log.warning(",[32,1643,270],{"class":82},[32,1645,1646],{"class":124},"\"Calculation failed: ",[32,1648,276],{"class":114},[32,1650,1266],{"class":86},[32,1652,288],{"class":114},[32,1654,273],{"class":124},[32,1656,339],{"class":86},[32,1658,1659,1661],{"class":34,"line":177},[32,1660,905],{"class":82},[32,1662,1663],{"class":114}," None\n",[1346,1665,1666,1667,1669,1670,1673,1674,1677],{},"A bare ",[29,1668,1528],{}," (or the nearly-as-broad ",[29,1671,1672],{},"except Exception:"," used indiscriminately) turns programming errors — typos, missing imports, wrong argument counts — into silent ",[29,1675,1676],{},"None"," returns instead of loud tracebacks, which is one of the most common sources of \"it fails silently in production but works in my tests\" bugs.",[14,1679,1681],{"id":1680},"custom-exceptions","Custom Exceptions",[19,1683,1684],{"language":21},[23,1685,1687],{"className":25,"code":1686,"language":21,"meta":27,"style":27},"class AppError(Exception):\n    \"\"\"Base exception for all application-specific errors.\"\"\"\n\nclass InsufficientFundsError(AppError):\n    def __init__(self, balance, requested):\n        self.balance = balance\n        self.requested = requested\n        super().__init__(f\"Cannot withdraw {requested}: balance is only {balance}\")\n\nclass AccountFrozenError(AppError):\n    pass\n\ndef withdraw(balance, amount):\n    if amount > balance:\n        raise InsufficientFundsError(balance, amount)\n    return balance - amount\n\ntry:\n    withdraw(100, 250)\nexcept InsufficientFundsError as e:\n    print(e)                    # Cannot withdraw 250: balance is only 100\n    print(e.balance, e.requested)  # 100 250 — custom attributes for programmatic handling\nexcept AppError:\n    print(\"Some other app error\")\n",[29,1688,1689,1702,1707,1711,1725,1734,1746,1758,1794,1798,1811,1815,1819,1829,1841,1848,1860,1864,1870,1885,1896,1906,1916,1923],{"__ignoreMap":27},[32,1690,1691,1693,1696,1698,1700],{"class":34,"line":35},[32,1692,104],{"class":82},[32,1694,1695],{"class":107}," AppError",[32,1697,111],{"class":86},[32,1699,115],{"class":114},[32,1701,118],{"class":86},[32,1703,1704],{"class":34,"line":42},[32,1705,1706],{"class":124},"    \"\"\"Base exception for all application-specific errors.\"\"\"\n",[32,1708,1709],{"class":34,"line":48},[32,1710,76],{"emptyLinePlaceholder":75},[32,1712,1713,1715,1718,1720,1723],{"class":34,"line":54},[32,1714,104],{"class":82},[32,1716,1717],{"class":107}," InsufficientFundsError",[32,1719,111],{"class":86},[32,1721,1722],{"class":107},"AppError",[32,1724,118],{"class":86},[32,1726,1727,1729,1731],{"class":34,"line":60},[32,1728,131],{"class":82},[32,1730,134],{"class":114},[32,1732,1733],{"class":86},"(self, balance, requested):\n",[32,1735,1736,1738,1741,1743],{"class":34,"line":66},[32,1737,180],{"class":114},[32,1739,1740],{"class":86},".balance ",[32,1742,186],{"class":82},[32,1744,1745],{"class":86}," balance\n",[32,1747,1748,1750,1753,1755],{"class":34,"line":72},[32,1749,180],{"class":114},[32,1751,1752],{"class":86},".requested ",[32,1754,186],{"class":82},[32,1756,1757],{"class":86}," requested\n",[32,1759,1760,1762,1764,1766,1768,1770,1773,1775,1778,1780,1783,1785,1788,1790,1792],{"class":34,"line":79},[32,1761,165],{"class":114},[32,1763,168],{"class":86},[32,1765,171],{"class":114},[32,1767,111],{"class":86},[32,1769,270],{"class":82},[32,1771,1772],{"class":124},"\"Cannot withdraw ",[32,1774,276],{"class":114},[32,1776,1777],{"class":86},"requested",[32,1779,288],{"class":114},[32,1781,1782],{"class":124},": balance is only ",[32,1784,276],{"class":114},[32,1786,1787],{"class":86},"balance",[32,1789,288],{"class":114},[32,1791,273],{"class":124},[32,1793,339],{"class":86},[32,1795,1796],{"class":34,"line":96},[32,1797,76],{"emptyLinePlaceholder":75},[32,1799,1800,1802,1805,1807,1809],{"class":34,"line":101},[32,1801,104],{"class":82},[32,1803,1804],{"class":107}," AccountFrozenError",[32,1806,111],{"class":86},[32,1808,1722],{"class":107},[32,1810,118],{"class":86},[32,1812,1813],{"class":34,"line":121},[32,1814,370],{"class":82},[32,1816,1817],{"class":34,"line":128},[32,1818,76],{"emptyLinePlaceholder":75},[32,1820,1821,1823,1826],{"class":34,"line":162},[32,1822,477],{"class":82},[32,1824,1825],{"class":107}," withdraw",[32,1827,1828],{"class":86},"(balance, amount):\n",[32,1830,1831,1833,1836,1838],{"class":34,"line":177},[32,1832,522],{"class":82},[32,1834,1835],{"class":86}," amount ",[32,1837,721],{"class":82},[32,1839,1840],{"class":86}," balance:\n",[32,1842,1843,1845],{"class":34,"line":195},[32,1844,767],{"class":82},[32,1846,1847],{"class":86}," InsufficientFundsError(balance, amount)\n",[32,1849,1850,1852,1855,1857],{"class":34,"line":200},[32,1851,784],{"class":82},[32,1853,1854],{"class":86}," balance ",[32,1856,1171],{"class":82},[32,1858,1859],{"class":86}," amount\n",[32,1861,1862],{"class":34,"line":215},[32,1863,76],{"emptyLinePlaceholder":75},[32,1865,1866,1868],{"class":34,"line":221},[32,1867,1219],{"class":82},[32,1869,494],{"class":86},[32,1871,1872,1875,1878,1880,1883],{"class":34,"line":240},[32,1873,1874],{"class":86},"    withdraw(",[32,1876,1877],{"class":114},"100",[32,1879,143],{"class":86},[32,1881,1882],{"class":114},"250",[32,1884,339],{"class":86},[32,1886,1887,1889,1892,1894],{"class":34,"line":256},[32,1888,1242],{"class":82},[32,1890,1891],{"class":86}," InsufficientFundsError ",[32,1893,1062],{"class":82},[32,1895,939],{"class":86},[32,1897,1898,1900,1903],{"class":34,"line":319},[32,1899,1254],{"class":114},[32,1901,1902],{"class":86},"(e)                    ",[32,1904,1905],{"class":38},"# Cannot withdraw 250: balance is only 100\n",[32,1907,1908,1910,1913],{"class":34,"line":342},[32,1909,1254],{"class":114},[32,1911,1912],{"class":86},"(e.balance, e.requested)  ",[32,1914,1915],{"class":38},"# 100 250 — custom attributes for programmatic handling\n",[32,1917,1918,1920],{"class":34,"line":347},[32,1919,1242],{"class":82},[32,1921,1922],{"class":86}," AppError:\n",[32,1924,1925,1927,1929,1932],{"class":34,"line":361},[32,1926,1254],{"class":114},[32,1928,111],{"class":86},[32,1930,1931],{"class":124},"\"Some other app error\"",[32,1933,339],{"class":86},[1346,1935,1936,1939,1940,1942,1943,1946,1947,1950],{},[1353,1937,1938],{},"Best practice",": define one base exception per application\u002Flibrary (",[29,1941,1722],{},") and derive specific exceptions from it — callers can catch broadly (",[29,1944,1945],{},"except AppError",") when they just want \"anything my own code raised\" or narrowly (",[29,1948,1949],{},"except InsufficientFundsError",") when they need to branch on the specific failure, and third-party exceptions never get accidentally conflated with your own.",[14,1952,1954,1957],{"id":1953},"raise-from-exception-chaining",[29,1955,1956],{},"raise ... from"," — Exception Chaining",[19,1959,1960],{"language":21},[23,1961,1963],{"className":25,"code":1962,"language":21,"meta":27,"style":27},"def load_user_config(path):\n    try:\n        with open(path) as f:\n            return f.read()\n    except FileNotFoundError as e:\n        raise AppError(f\"Could not load config from {path}\") from e   # preserves the ORIGINAL cause\n\ntry:\n    load_user_config(\"\u002Fnonexistent\u002Fconfig.json\")\nexcept AppError as e:\n    print(e)                # Could not load config from \u002Fnonexistent\u002Fconfig.json\n    print(e.__cause__)        # [Errno 2] No such file or directory: '\u002Fnonexistent\u002Fconfig.json'\n",[29,1964,1965,1975,1981,1997,2005,2016,2047,2051,2057,2067,2078,2088],{"__ignoreMap":27},[32,1966,1967,1969,1972],{"class":34,"line":35},[32,1968,477],{"class":82},[32,1970,1971],{"class":107}," load_user_config",[32,1973,1974],{"class":86},"(path):\n",[32,1976,1977,1979],{"class":34,"line":42},[32,1978,848],{"class":82},[32,1980,494],{"class":86},[32,1982,1983,1986,1989,1992,1994],{"class":34,"line":48},[32,1984,1985],{"class":82},"        with",[32,1987,1988],{"class":114}," open",[32,1990,1991],{"class":86},"(path) ",[32,1993,1062],{"class":82},[32,1995,1996],{"class":86}," f:\n",[32,1998,1999,2002],{"class":34,"line":54},[32,2000,2001],{"class":82},"            return",[32,2003,2004],{"class":86}," f.read()\n",[32,2006,2007,2009,2012,2014],{"class":34,"line":60},[32,2008,931],{"class":82},[32,2010,2011],{"class":114}," FileNotFoundError",[32,2013,936],{"class":82},[32,2015,939],{"class":86},[32,2017,2018,2020,2023,2025,2028,2030,2033,2035,2037,2039,2041,2044],{"class":34,"line":66},[32,2019,767],{"class":82},[32,2021,2022],{"class":86}," AppError(",[32,2024,270],{"class":82},[32,2026,2027],{"class":124},"\"Could not load config from ",[32,2029,276],{"class":114},[32,2031,2032],{"class":86},"path",[32,2034,288],{"class":114},[32,2036,273],{"class":124},[32,2038,968],{"class":86},[32,2040,83],{"class":82},[32,2042,2043],{"class":86}," e   ",[32,2045,2046],{"class":38},"# preserves the ORIGINAL cause\n",[32,2048,2049],{"class":34,"line":72},[32,2050,76],{"emptyLinePlaceholder":75},[32,2052,2053,2055],{"class":34,"line":79},[32,2054,1219],{"class":82},[32,2056,494],{"class":86},[32,2058,2059,2062,2065],{"class":34,"line":96},[32,2060,2061],{"class":86},"    load_user_config(",[32,2063,2064],{"class":124},"\"\u002Fnonexistent\u002Fconfig.json\"",[32,2066,339],{"class":86},[32,2068,2069,2071,2074,2076],{"class":34,"line":101},[32,2070,1242],{"class":82},[32,2072,2073],{"class":86}," AppError ",[32,2075,1062],{"class":82},[32,2077,939],{"class":86},[32,2079,2080,2082,2085],{"class":34,"line":121},[32,2081,1254],{"class":114},[32,2083,2084],{"class":86},"(e)                ",[32,2086,2087],{"class":38},"# Could not load config from \u002Fnonexistent\u002Fconfig.json\n",[32,2089,2090,2092,2095],{"class":34,"line":128},[32,2091,1254],{"class":114},[32,2093,2094],{"class":86},"(e.__cause__)        ",[32,2096,2097],{"class":38},"# [Errno 2] No such file or directory: '\u002Fnonexistent\u002Fconfig.json'\n",[1346,2099,2100,2101,2104,2105,2108,2109,2112],{},"Without ",[29,2102,2103],{},"from e",", the traceback still shows both exceptions (Python automatically records the original as ",[29,2106,2107],{},"__context__"," during exception handling), but labeled as \"During handling of the above exception, another exception occurred\" — implying an accidental, unrelated failure. ",[29,2110,2111],{},"raise ... from e"," instead marks it explicitly as \"The above exception was the direct cause,\" which is the more accurate and more debuggable story when you're deliberately translating one exception type into another.",[1434,2114,2116],{"id":2115},"suppressing-the-chain-entirely","Suppressing the chain entirely",[19,2118,2119],{"language":21},[23,2120,2122],{"className":25,"code":2121,"language":21,"meta":27,"style":27},"try:\n    int(\"not a number\")\nexcept ValueError:\n    raise RuntimeError(\"Config value must be numeric\") from None   # hides the ValueError entirely\n\n# Traceback shows ONLY the RuntimeError — appropriate when the original exception\n# is pure noise for the caller (e.g., an internal implementation detail)\n",[29,2123,2124,2130,2142,2151,2173,2177,2182],{"__ignoreMap":27},[32,2125,2126,2128],{"class":34,"line":35},[32,2127,1219],{"class":82},[32,2129,494],{"class":86},[32,2131,2132,2135,2137,2140],{"class":34,"line":42},[32,2133,2134],{"class":114},"    int",[32,2136,111],{"class":86},[32,2138,2139],{"class":124},"\"not a number\"",[32,2141,339],{"class":86},[32,2143,2144,2146,2149],{"class":34,"line":48},[32,2145,1242],{"class":82},[32,2147,2148],{"class":114}," ValueError",[32,2150,494],{"class":86},[32,2152,2153,2156,2159,2161,2164,2166,2168,2170],{"class":34,"line":54},[32,2154,2155],{"class":82},"    raise",[32,2157,2158],{"class":114}," RuntimeError",[32,2160,111],{"class":86},[32,2162,2163],{"class":124},"\"Config value must be numeric\"",[32,2165,968],{"class":86},[32,2167,83],{"class":82},[32,2169,637],{"class":114},[32,2171,2172],{"class":38},"   # hides the ValueError entirely\n",[32,2174,2175],{"class":34,"line":60},[32,2176,76],{"emptyLinePlaceholder":75},[32,2178,2179],{"class":34,"line":66},[32,2180,2181],{"class":38},"# Traceback shows ONLY the RuntimeError — appropriate when the original exception\n",[32,2183,2184],{"class":34,"line":72},[32,2185,2186],{"class":38},"# is pure noise for the caller (e.g., an internal implementation detail)\n",[14,2188,2190],{"id":2189},"custom-exceptions-with-structured-data","Custom Exceptions with Structured Data",[19,2192,2193],{"language":21},[23,2194,2196],{"className":25,"code":2195,"language":21,"meta":27,"style":27},"class ValidationError(Exception):\n    def __init__(self, errors):\n        self.errors = errors    # a list of field-level error dicts\n        message = f\"{len(errors)} validation error(s): \" + \", \".join(e[\"field\"] for e in errors)\n        super().__init__(message)\n\ndef validate_user(data):\n    errors = []\n    if not data.get(\"email\"):\n        errors.append({\"field\": \"email\", \"message\": \"required\"})\n    if data.get(\"age\", 0) \u003C 0:\n        errors.append({\"field\": \"age\", \"message\": \"must be non-negative\"})\n    if errors:\n        raise ValidationError(errors)\n    return data\n\ntry:\n    validate_user({\"age\": -5})\nexcept ValidationError as e:\n    print(e)                     # 2 validation error(s): email, age\n    for err in e.errors:\n        print(f\"  {err['field']}: {err['message']}\")\n",[29,2197,2198,2210,2219,2233,2278,2288,2292,2302,2310,2323,2343,2364,2385,2391,2398,2405,2409,2415,2430,2440,2450,2463],{"__ignoreMap":27},[32,2199,2200,2202,2204,2206,2208],{"class":34,"line":35},[32,2201,104],{"class":82},[32,2203,205],{"class":107},[32,2205,111],{"class":86},[32,2207,115],{"class":114},[32,2209,118],{"class":86},[32,2211,2212,2214,2216],{"class":34,"line":42},[32,2213,131],{"class":82},[32,2215,134],{"class":114},[32,2217,2218],{"class":86},"(self, errors):\n",[32,2220,2221,2223,2225,2227,2230],{"class":34,"line":48},[32,2222,180],{"class":114},[32,2224,245],{"class":86},[32,2226,186],{"class":82},[32,2228,2229],{"class":86}," errors    ",[32,2231,2232],{"class":38},"# a list of field-level error dicts\n",[32,2234,2235,2237,2239,2242,2244,2247,2250,2252,2255,2258,2261,2264,2266,2269,2272,2274,2276],{"class":34,"line":54},[32,2236,259],{"class":86},[32,2238,186],{"class":82},[32,2240,2241],{"class":82}," f",[32,2243,273],{"class":124},[32,2245,2246],{"class":114},"{len",[32,2248,2249],{"class":86},"(errors)",[32,2251,288],{"class":114},[32,2253,2254],{"class":124}," validation error(s): \"",[32,2256,2257],{"class":82}," +",[32,2259,2260],{"class":124}," \", \"",[32,2262,2263],{"class":86},".join(e[",[32,2265,542],{"class":124},[32,2267,2268],{"class":86},"] ",[32,2270,2271],{"class":82},"for",[32,2273,310],{"class":86},[32,2275,313],{"class":82},[32,2277,316],{"class":86},[32,2279,2280,2282,2284,2286],{"class":34,"line":60},[32,2281,165],{"class":114},[32,2283,168],{"class":86},[32,2285,171],{"class":114},[32,2287,174],{"class":86},[32,2289,2290],{"class":34,"line":66},[32,2291,76],{"emptyLinePlaceholder":75},[32,2293,2294,2296,2299],{"class":34,"line":72},[32,2295,477],{"class":82},[32,2297,2298],{"class":107}," validate_user",[32,2300,2301],{"class":86},"(data):\n",[32,2303,2304,2306,2308],{"class":34,"line":79},[32,2305,506],{"class":86},[32,2307,186],{"class":82},[32,2309,511],{"class":86},[32,2311,2312,2314,2316,2319,2321],{"class":34,"line":96},[32,2313,522],{"class":82},[32,2315,525],{"class":82},[32,2317,2318],{"class":86}," data.get(",[32,2320,531],{"class":124},[32,2322,118],{"class":86},[32,2324,2325,2327,2329,2331,2333,2335,2337,2339,2341],{"class":34,"line":101},[32,2326,539],{"class":86},[32,2328,542],{"class":124},[32,2330,291],{"class":86},[32,2332,531],{"class":124},[32,2334,143],{"class":86},[32,2336,551],{"class":124},[32,2338,291],{"class":86},[32,2340,556],{"class":124},[32,2342,559],{"class":86},[32,2344,2345,2347,2349,2351,2353,2356,2358,2360,2362],{"class":34,"line":121},[32,2346,522],{"class":82},[32,2348,2318],{"class":86},[32,2350,621],{"class":124},[32,2352,143],{"class":86},[32,2354,2355],{"class":114},"0",[32,2357,968],{"class":86},[32,2359,710],{"class":82},[32,2361,713],{"class":114},[32,2363,494],{"class":86},[32,2365,2366,2368,2370,2372,2374,2376,2378,2380,2383],{"class":34,"line":128},[32,2367,539],{"class":86},[32,2369,542],{"class":124},[32,2371,291],{"class":86},[32,2373,621],{"class":124},[32,2375,143],{"class":86},[32,2377,551],{"class":124},[32,2379,291],{"class":86},[32,2381,2382],{"class":124},"\"must be non-negative\"",[32,2384,559],{"class":86},[32,2386,2387,2389],{"class":34,"line":162},[32,2388,522],{"class":82},[32,2390,761],{"class":86},[32,2392,2393,2395],{"class":34,"line":177},[32,2394,767],{"class":82},[32,2396,2397],{"class":86}," ValidationError(errors)\n",[32,2399,2400,2402],{"class":34,"line":195},[32,2401,784],{"class":82},[32,2403,2404],{"class":86}," data\n",[32,2406,2407],{"class":34,"line":200},[32,2408,76],{"emptyLinePlaceholder":75},[32,2410,2411,2413],{"class":34,"line":215},[32,2412,1219],{"class":82},[32,2414,494],{"class":86},[32,2416,2417,2420,2422,2424,2426,2428],{"class":34,"line":221},[32,2418,2419],{"class":86},"    validate_user({",[32,2421,621],{"class":124},[32,2423,291],{"class":86},[32,2425,1171],{"class":82},[32,2427,1174],{"class":114},[32,2429,559],{"class":86},[32,2431,2432,2434,2436,2438],{"class":34,"line":240},[32,2433,1242],{"class":82},[32,2435,1059],{"class":86},[32,2437,1062],{"class":82},[32,2439,939],{"class":86},[32,2441,2442,2444,2447],{"class":34,"line":256},[32,2443,1254],{"class":114},[32,2445,2446],{"class":86},"(e)                     ",[32,2448,2449],{"class":38},"# 2 validation error(s): email, age\n",[32,2451,2452,2455,2458,2460],{"class":34,"line":319},[32,2453,2454],{"class":82},"    for",[32,2456,2457],{"class":86}," err ",[32,2459,313],{"class":82},[32,2461,2462],{"class":86}," e.errors:\n",[32,2464,2465,2468,2470,2472,2475,2477,2480,2482,2484,2486,2488,2490,2492,2494,2496,2498,2500],{"class":34,"line":342},[32,2466,2467],{"class":114},"        print",[32,2469,111],{"class":86},[32,2471,270],{"class":82},[32,2473,2474],{"class":124},"\"  ",[32,2476,276],{"class":114},[32,2478,2479],{"class":86},"err[",[32,2481,282],{"class":124},[32,2483,285],{"class":86},[32,2485,288],{"class":114},[32,2487,291],{"class":124},[32,2489,276],{"class":114},[32,2491,2479],{"class":86},[32,2493,298],{"class":124},[32,2495,285],{"class":86},[32,2497,288],{"class":114},[32,2499,273],{"class":124},[32,2501,339],{"class":86},[1346,2503,2504,2505,2509,2510,2513],{},"Attaching structured data (not just a message string) to custom exceptions lets calling code programmatically react to ",[2506,2507,2508],"em",{},"what"," went wrong — an API layer can turn ",[29,2511,2512],{},"e.errors"," directly into a JSON error response, something impossible if the exception only carries a human-readable string.",[14,2515,2517,2518,2520,2521,2524,2525],{"id":2516},"context-managers-for-cleanup-tryfinally-vs-with","Context Managers for Cleanup: ",[29,2519,1219],{},"\u002F",[29,2522,2523],{},"finally"," vs ",[29,2526,2527],{},"with",[19,2529,2530],{"language":21},[23,2531,2533],{"className":25,"code":2532,"language":21,"meta":27,"style":27},"# Manual try\u002Ffinally — correct, but verbose and easy to get wrong with multiple resources\nlock = threading.Lock()\nlock.acquire()\ntry:\n    do_work()\nfinally:\n    lock.release()\n\n# Idiomatic — the context manager protocol (chapter 14\u002F19) guarantees the same cleanup, less code\nwith threading.Lock():\n    do_work()\n",[29,2534,2535,2540,2550,2555,2561,2566,2572,2577,2581,2586,2593],{"__ignoreMap":27},[32,2536,2537],{"class":34,"line":35},[32,2538,2539],{"class":38},"# Manual try\u002Ffinally — correct, but verbose and easy to get wrong with multiple resources\n",[32,2541,2542,2545,2547],{"class":34,"line":42},[32,2543,2544],{"class":86},"lock ",[32,2546,186],{"class":82},[32,2548,2549],{"class":86}," threading.Lock()\n",[32,2551,2552],{"class":34,"line":48},[32,2553,2554],{"class":86},"lock.acquire()\n",[32,2556,2557,2559],{"class":34,"line":54},[32,2558,1219],{"class":82},[32,2560,494],{"class":86},[32,2562,2563],{"class":34,"line":60},[32,2564,2565],{"class":86},"    do_work()\n",[32,2567,2568,2570],{"class":34,"line":66},[32,2569,2523],{"class":82},[32,2571,494],{"class":86},[32,2573,2574],{"class":34,"line":72},[32,2575,2576],{"class":86},"    lock.release()\n",[32,2578,2579],{"class":34,"line":79},[32,2580,76],{"emptyLinePlaceholder":75},[32,2582,2583],{"class":34,"line":96},[32,2584,2585],{"class":38},"# Idiomatic — the context manager protocol (chapter 14\u002F19) guarantees the same cleanup, less code\n",[32,2587,2588,2590],{"class":34,"line":101},[32,2589,2527],{"class":82},[32,2591,2592],{"class":86}," threading.Lock():\n",[32,2594,2595],{"class":34,"line":121},[32,2596,2565],{"class":86},[1346,2598,2599,2600,2602,2603,2520,2605,2607,2608,2610,2611,2614],{},"Any cleanup that must happen \"no matter what\" — closing files, releasing locks, rolling back transactions, disconnecting sockets — is a candidate for ",[29,2601,2527],{}," if the object supports it, and a candidate for ",[29,2604,1219],{},[29,2606,2523],{}," if it doesn't. ",[29,2609,2523],{}," and ",[29,2612,2613],{},"__exit__"," share the exact same guarantee: they run even if the protected code returns early, breaks out of a loop, or raises.",[14,2616,2618,2619,2621],{"id":2617},"multiple-except-types-and-exception-groups","Multiple ",[29,2620,1242],{}," Types and Exception Groups",[19,2623,2624],{"language":21},[23,2625,2627],{"className":25,"code":2626,"language":21,"meta":27,"style":27},"try:\n    value = int(input_str) \u002F divisor\nexcept (ValueError, ZeroDivisionError) as e:    # one clause, multiple types — tuple syntax\n    print(f\"Invalid input: {e}\")\n",[29,2628,2629,2635,2653,2675],{"__ignoreMap":27},[32,2630,2631,2633],{"class":34,"line":35},[32,2632,1219],{"class":82},[32,2634,494],{"class":86},[32,2636,2637,2640,2642,2645,2648,2650],{"class":34,"line":42},[32,2638,2639],{"class":86},"    value ",[32,2641,186],{"class":82},[32,2643,2644],{"class":114}," int",[32,2646,2647],{"class":86},"(input_str) ",[32,2649,2520],{"class":82},[32,2651,2652],{"class":86}," divisor\n",[32,2654,2655,2657,2659,2661,2663,2665,2667,2669,2672],{"class":34,"line":48},[32,2656,1242],{"class":82},[32,2658,1622],{"class":86},[32,2660,1625],{"class":114},[32,2662,143],{"class":86},[32,2664,1630],{"class":114},[32,2666,968],{"class":86},[32,2668,1062],{"class":82},[32,2670,2671],{"class":86}," e:    ",[32,2673,2674],{"class":38},"# one clause, multiple types — tuple syntax\n",[32,2676,2677,2679,2681,2683,2686,2688,2690,2692,2694],{"class":34,"line":54},[32,2678,1254],{"class":114},[32,2680,111],{"class":86},[32,2682,270],{"class":82},[32,2684,2685],{"class":124},"\"Invalid input: ",[32,2687,276],{"class":114},[32,2689,1266],{"class":86},[32,2691,288],{"class":114},[32,2693,273],{"class":124},[32,2695,339],{"class":86},[19,2697,2698],{"language":21},[23,2699,2701],{"className":25,"code":2700,"language":21,"meta":27,"style":27},"# Python 3.11+ — ExceptionGroup and except* for handling multiple concurrent failures\ntry:\n    raise ExceptionGroup(\"multiple failures\", [\n        ValueError(\"bad value\"),\n        TypeError(\"bad type\"),\n    ])\nexcept* ValueError as eg:\n    print(f\"Handled ValueErrors: {eg.exceptions}\")\nexcept* TypeError as eg:\n    print(f\"Handled TypeErrors: {eg.exceptions}\")\n",[29,2702,2703,2708,2714,2727,2740,2752,2757,2769,2791,2802],{"__ignoreMap":27},[32,2704,2705],{"class":34,"line":35},[32,2706,2707],{"class":38},"# Python 3.11+ — ExceptionGroup and except* for handling multiple concurrent failures\n",[32,2709,2710,2712],{"class":34,"line":42},[32,2711,1219],{"class":82},[32,2713,494],{"class":86},[32,2715,2716,2718,2721,2724],{"class":34,"line":48},[32,2717,2155],{"class":82},[32,2719,2720],{"class":86}," ExceptionGroup(",[32,2722,2723],{"class":124},"\"multiple failures\"",[32,2725,2726],{"class":86},", [\n",[32,2728,2729,2732,2734,2737],{"class":34,"line":54},[32,2730,2731],{"class":114},"        ValueError",[32,2733,111],{"class":86},[32,2735,2736],{"class":124},"\"bad value\"",[32,2738,2739],{"class":86},"),\n",[32,2741,2742,2745,2747,2750],{"class":34,"line":60},[32,2743,2744],{"class":114},"        TypeError",[32,2746,111],{"class":86},[32,2748,2749],{"class":124},"\"bad type\"",[32,2751,2739],{"class":86},[32,2753,2754],{"class":34,"line":66},[32,2755,2756],{"class":86},"    ])\n",[32,2758,2759,2762,2764,2766],{"class":34,"line":72},[32,2760,2761],{"class":82},"except*",[32,2763,2148],{"class":114},[32,2765,936],{"class":82},[32,2767,2768],{"class":86}," eg:\n",[32,2770,2771,2773,2775,2777,2780,2782,2785,2787,2789],{"class":34,"line":79},[32,2772,1254],{"class":114},[32,2774,111],{"class":86},[32,2776,270],{"class":82},[32,2778,2779],{"class":124},"\"Handled ValueErrors: ",[32,2781,276],{"class":114},[32,2783,2784],{"class":86},"eg.exceptions",[32,2786,288],{"class":114},[32,2788,273],{"class":124},[32,2790,339],{"class":86},[32,2792,2793,2795,2798,2800],{"class":34,"line":96},[32,2794,2761],{"class":82},[32,2796,2797],{"class":114}," TypeError",[32,2799,936],{"class":82},[32,2801,2768],{"class":86},[32,2803,2804,2806,2808,2810,2813,2815,2817,2819,2821],{"class":34,"line":101},[32,2805,1254],{"class":114},[32,2807,111],{"class":86},[32,2809,270],{"class":82},[32,2811,2812],{"class":124},"\"Handled TypeErrors: ",[32,2814,276],{"class":114},[32,2816,2784],{"class":86},[32,2818,288],{"class":114},[32,2820,273],{"class":124},[32,2822,339],{"class":86},[1346,2824,2825,2520,2828,2830,2831,2834,2835,2838,2839,2841],{},[29,2826,2827],{},"ExceptionGroup",[29,2829,2761],{}," (3.11+) exist because some operations — notably ",[29,2832,2833],{},"asyncio.TaskGroup"," (chapter 22) — can genuinely fail with ",[2506,2836,2837],{},"multiple independent exceptions at once",", which the traditional single-exception model has no way to represent; ",[29,2840,2761],{}," can match and handle each sub-exception type from the group independently.",[14,2843,2845,2848],{"id":2844},"assert-is-not-error-handling",[29,2846,2847],{},"assert"," Is Not Error Handling",[19,2850,2851],{"language":21},[23,2852,2854],{"className":25,"code":2853,"language":21,"meta":27,"style":27},"def set_discount(percent):\n    assert 0 \u003C= percent \u003C= 100, \"percent must be between 0 and 100\"\n    return percent \u002F 100\n\n# python -O my_script.py   # runs with optimizations ON — assert statements are STRIPPED ENTIRELY\n",[29,2855,2856,2866,2890,2901,2905],{"__ignoreMap":27},[32,2857,2858,2860,2863],{"class":34,"line":35},[32,2859,477],{"class":82},[32,2861,2862],{"class":107}," set_discount",[32,2864,2865],{"class":86},"(percent):\n",[32,2867,2868,2871,2873,2876,2879,2882,2885,2887],{"class":34,"line":42},[32,2869,2870],{"class":82},"    assert",[32,2872,713],{"class":114},[32,2874,2875],{"class":82}," \u003C=",[32,2877,2878],{"class":86}," percent ",[32,2880,2881],{"class":82},"\u003C=",[32,2883,2884],{"class":114}," 100",[32,2886,143],{"class":86},[32,2888,2889],{"class":124},"\"percent must be between 0 and 100\"\n",[32,2891,2892,2894,2896,2898],{"class":34,"line":48},[32,2893,784],{"class":82},[32,2895,2878],{"class":86},[32,2897,2520],{"class":82},[32,2899,2900],{"class":114}," 100\n",[32,2902,2903],{"class":34,"line":54},[32,2904,76],{"emptyLinePlaceholder":75},[32,2906,2907],{"class":34,"line":60},[32,2908,2909],{"class":38},"# python -O my_script.py   # runs with optimizations ON — assert statements are STRIPPED ENTIRELY\n",[1346,2911,2912,2914,2915,2918,2919,2921,2922,2928,2929,2931],{},[29,2913,2847],{}," statements are removed completely when Python runs with the ",[29,2916,2917],{},"-O"," (optimize) flag — meaning ",[29,2920,2847],{},"-based validation silently vanishes in that mode. ",[1353,2923,2924,2925,2927],{},"Never use ",[29,2926,2847],{}," for input validation, security checks, or anything that must always run"," — use it only for internal invariants\u002Fdebugging aids that are fine to disable in production, and raise real exceptions (",[29,2930,1625],{},", a custom exception) for anything that represents genuinely invalid input.",[14,2933,2935],{"id":2934},"tips-tricks","💡 Tips & Tricks",[2937,2938,2939,2952,2971,2979,2994],"ul",{},[2940,2941,2942,2945,2946,2948,2949,2951],"li",{},[1353,2943,2944],{},"Idiom",": catch the narrowest exception type that lets you handle the failure meaningfully — ",[29,2947,1494],{}," (or worse, bare ",[29,2950,1528],{},") as a habit turns real bugs into silent failures indistinguishable from expected error conditions.",[2940,2953,2954,291,2957,1503,2960,2963,2964,2966,2967,2970],{},[1353,2955,2956],{},"Debug",[29,2958,2959],{},"traceback.print_exc()",[29,2961,2962],{},"logging.exception(...)"," inside an ",[29,2965,1242],{}," block preserves the full traceback for debugging, unlike ",[29,2968,2969],{},"print(e)",", which shows only the exception's string message and loses the call stack.",[2940,2972,2973,2975,2976,2978],{},[1353,2974,2944],{},": use ",[29,2977,2111],{}," when deliberately translating a low-level exception into a higher-level, more meaningful one — it keeps the original cause visible in the traceback instead of looking like an unrelated second failure.",[2940,2980,2981,2983,2984,2987,2988,2990,2991,2993],{},[1353,2982,2944],{},": put the \"happy path\" continuation in ",[29,2985,2986],{},"else",", not at the end of ",[29,2989,1219],{}," — this keeps the ",[29,2992,1219],{}," block scoped tightly to just the line(s) that can actually raise, so you don't accidentally catch an exception from code that was never meant to be protected.",[2940,2995,2996,291,2998,2610,3001,3004],{},[1353,2997,2956],{},[29,2999,3000],{},"sys.exc_info()",[29,3002,3003],{},"e.__traceback__"," give you programmatic access to the current exception's traceback object — useful for custom logging\u002Freporting frameworks that need to format or forward exception details.",[14,3006,3008],{"id":3007},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[2937,3010,3011,3029,3040,3059,3084],{},[2940,3012,3013,3028],{},[1353,3014,1666,3015,3017,3018,3020,3021,143,3024,3027],{},[29,3016,1528],{}," (or careless ",[29,3019,1672],{},") silently swallows ",[29,3022,3023],{},"NameError",[29,3025,3026],{},"TypeError",", and other bugs alongside the errors you meant to handle"," — this is the single most common way real programming mistakes get hidden until they cause confusing behavior far from the actual bug.",[2940,3030,3031,3036,3037,3039],{},[1353,3032,3033,3035],{},[29,3034,1242],{}," clause order matters — a broader exception type listed before a narrower (subclass) one makes the narrower clause permanently unreachable",", with no warning from the interpreter; always order ",[29,3038,1242],{}," clauses from most specific to most general.",[2940,3041,3042,3052,3053,3055,3056,3058],{},[1353,3043,3044,3046,3047,2520,3049],{},[29,3045,2847],{}," statements are completely stripped when Python runs with ",[29,3048,2917],{},[29,3050,3051],{},"-OO"," — code that relies on ",[29,3054,2847],{}," for validation that must always happen (input sanitization, security checks) has a silent, environment-dependent bug: it works in development, and stops validating anything the moment ",[29,3057,2917],{}," is used.",[2940,3060,3061,3076,3077,3079,3080,3083],{},[1353,3062,3063,3065,3066,1503,3068,3070,3071,3073,3074],{},[29,3064,1494],{}," does not catch ",[29,3067,1506],{},[29,3069,1502],{},", because both inherit from ",[29,3072,1510],{}," directly, not ",[29,3075,115],{}," — this is usually desirable (Ctrl+C should still work inside a broad ",[29,3078,1219],{},"), but code that genuinely needs to catch everything, including these, must explicitly write ",[29,3081,3082],{},"except BaseException",", and should almost always re-raise after cleanup rather than suppress them.",[2940,3085,3086,3109,3110,3112,3113,3112,3116,3118,3119,3121,3122,2520,3124,3126,3127,3129,3130,1521],{},[1353,3087,3088,3089,143,3092,3095,3096,3099,3100,3102,3103,3105,3106,3108],{},"A ",[29,3090,3091],{},"return",[29,3093,3094],{},"break",", or ",[29,3097,3098],{},"continue"," inside a ",[29,3101,1219],{}," block does NOT skip the ",[29,3104,2523],{}," clause — ",[29,3107,2523],{}," still runs before control actually leaves the block",", and a ",[29,3111,3091],{}," ",[2506,3114,3115],{},"inside",[29,3117,2523],{}," will silently override (discard) a ",[29,3120,3091],{}," or an in-flight exception from the ",[29,3123,1219],{},[29,3125,1242],{}," — a rare but genuinely confusing gotcha worth avoiding by never ",[29,3128,3091],{},"-ing from ",[29,3131,2523],{},[14,3133,3135],{"id":3134},"spot-the-bug","🧠 Spot the Bug",[1346,3137,3138],{},"A retry helper is supposed to retry a flaky operation up to 3 times, but it swallows a critical bug that should have crashed the program immediately. Find the issue.",[19,3140,3141],{"language":21},[23,3142,3144],{"className":25,"code":3143,"language":21,"meta":27,"style":27},"def fetch_with_retry(fetch_fn, attempts=3):\n    for attempt in range(attempts):\n        try:\n            return fetch_fn()\n        except:\n            print(f\"Attempt {attempt + 1} failed, retrying...\")\n    raise RuntimeError(\"All attempts failed\")\n\ndef fetch_data():\n    respones = {\"status\": \"ok\"}   # typo: should be `response`\n    return respones[\"status\"]\n\nfetch_with_retry(fetch_data)\n",[29,3145,3146,3163,3178,3185,3192,3199,3227,3240,3244,3253,3275,3287,3291],{"__ignoreMap":27},[32,3147,3148,3150,3153,3156,3158,3161],{"class":34,"line":35},[32,3149,477],{"class":82},[32,3151,3152],{"class":107}," fetch_with_retry",[32,3154,3155],{"class":86},"(fetch_fn, attempts",[32,3157,186],{"class":82},[32,3159,3160],{"class":114},"3",[32,3162,118],{"class":86},[32,3164,3165,3167,3170,3172,3175],{"class":34,"line":42},[32,3166,2454],{"class":82},[32,3168,3169],{"class":86}," attempt ",[32,3171,313],{"class":82},[32,3173,3174],{"class":114}," range",[32,3176,3177],{"class":86},"(attempts):\n",[32,3179,3180,3183],{"class":34,"line":48},[32,3181,3182],{"class":82},"        try",[32,3184,494],{"class":86},[32,3186,3187,3189],{"class":34,"line":54},[32,3188,2001],{"class":82},[32,3190,3191],{"class":86}," fetch_fn()\n",[32,3193,3194,3197],{"class":34,"line":60},[32,3195,3196],{"class":82},"        except",[32,3198,494],{"class":86},[32,3200,3201,3204,3206,3208,3211,3213,3216,3219,3222,3225],{"class":34,"line":66},[32,3202,3203],{"class":114},"            print",[32,3205,111],{"class":86},[32,3207,270],{"class":82},[32,3209,3210],{"class":124},"\"Attempt ",[32,3212,276],{"class":114},[32,3214,3215],{"class":86},"attempt ",[32,3217,3218],{"class":82},"+",[32,3220,3221],{"class":114}," 1}",[32,3223,3224],{"class":124}," failed, retrying...\"",[32,3226,339],{"class":86},[32,3228,3229,3231,3233,3235,3238],{"class":34,"line":72},[32,3230,2155],{"class":82},[32,3232,2158],{"class":114},[32,3234,111],{"class":86},[32,3236,3237],{"class":124},"\"All attempts failed\"",[32,3239,339],{"class":86},[32,3241,3242],{"class":34,"line":79},[32,3243,76],{"emptyLinePlaceholder":75},[32,3245,3246,3248,3251],{"class":34,"line":96},[32,3247,477],{"class":82},[32,3249,3250],{"class":107}," fetch_data",[32,3252,1550],{"class":86},[32,3254,3255,3258,3260,3262,3264,3266,3269,3272],{"class":34,"line":101},[32,3256,3257],{"class":86},"    respones ",[32,3259,186],{"class":82},[32,3261,908],{"class":86},[32,3263,1038],{"class":124},[32,3265,291],{"class":86},[32,3267,3268],{"class":124},"\"ok\"",[32,3270,3271],{"class":86},"}   ",[32,3273,3274],{"class":38},"# typo: should be `response`\n",[32,3276,3277,3279,3282,3284],{"class":34,"line":121},[32,3278,784],{"class":82},[32,3280,3281],{"class":86}," respones[",[32,3283,1038],{"class":124},[32,3285,3286],{"class":86},"]\n",[32,3288,3289],{"class":34,"line":128},[32,3290,76],{"emptyLinePlaceholder":75},[32,3292,3293],{"class":34,"line":162},[32,3294,3295],{"class":86},"fetch_with_retry(fetch_data)\n",[3297,3298,3299,3303,3341,3344,3452,3462],"details",{},[3300,3301,3302],"summary",{},"Answer",[1346,3304,3305,3308,3309,3312,3313,3316,3317,3319,3320,3323,3324,3327,3328,3331,3332,3335,3336,2520,3338,3340],{},[29,3306,3307],{},"fetch_data"," actually has no bug in the shown snippet — but suppose the real function had a genuine typo like ",[29,3310,3311],{},"response[\"staus\"]"," (a ",[29,3314,3315],{},"KeyError"," from a misspelled key, i.e., a real programming mistake, not a transient failure). The bare ",[29,3318,1528],{}," in ",[29,3321,3322],{},"fetch_with_retry"," catches ",[2506,3325,3326],{},"that"," exactly the same way it catches a legitimate transient failure (like a ",[29,3329,3330],{},"ConnectionError",") — it retries three times, fails three times for the exact same reason (the typo doesn't go away on retry), and then raises a generic ",[29,3333,3334],{},"RuntimeError(\"All attempts failed\")"," that completely hides the real ",[29,3337,3315],{},[29,3339,3023],{}," and its traceback.",[1346,3342,3343],{},"The fix is to only catch the specific, genuinely-transient exception types that retrying could plausibly fix, and let programming errors propagate immediately:",[19,3345,3346],{"language":21},[23,3347,3349],{"className":25,"code":3348,"language":21,"meta":27,"style":27},"def fetch_with_retry(fetch_fn, attempts=3):\n    for attempt in range(attempts):\n        try:\n            return fetch_fn()\n        except (ConnectionError, TimeoutError) as e:\n            print(f\"Attempt {attempt + 1} failed: {e}, retrying...\")\n    raise RuntimeError(\"All attempts failed\")\n",[29,3350,3351,3365,3377,3383,3389,3408,3440],{"__ignoreMap":27},[32,3352,3353,3355,3357,3359,3361,3363],{"class":34,"line":35},[32,3354,477],{"class":82},[32,3356,3152],{"class":107},[32,3358,3155],{"class":86},[32,3360,186],{"class":82},[32,3362,3160],{"class":114},[32,3364,118],{"class":86},[32,3366,3367,3369,3371,3373,3375],{"class":34,"line":42},[32,3368,2454],{"class":82},[32,3370,3169],{"class":86},[32,3372,313],{"class":82},[32,3374,3174],{"class":114},[32,3376,3177],{"class":86},[32,3378,3379,3381],{"class":34,"line":48},[32,3380,3182],{"class":82},[32,3382,494],{"class":86},[32,3384,3385,3387],{"class":34,"line":54},[32,3386,2001],{"class":82},[32,3388,3191],{"class":86},[32,3390,3391,3393,3395,3397,3399,3402,3404,3406],{"class":34,"line":60},[32,3392,3196],{"class":82},[32,3394,1622],{"class":86},[32,3396,3330],{"class":114},[32,3398,143],{"class":86},[32,3400,3401],{"class":114},"TimeoutError",[32,3403,968],{"class":86},[32,3405,1062],{"class":82},[32,3407,939],{"class":86},[32,3409,3410,3412,3414,3416,3418,3420,3422,3424,3426,3429,3431,3433,3435,3438],{"class":34,"line":66},[32,3411,3203],{"class":114},[32,3413,111],{"class":86},[32,3415,270],{"class":82},[32,3417,3210],{"class":124},[32,3419,276],{"class":114},[32,3421,3215],{"class":86},[32,3423,3218],{"class":82},[32,3425,3221],{"class":114},[32,3427,3428],{"class":124}," failed: ",[32,3430,276],{"class":114},[32,3432,1266],{"class":86},[32,3434,288],{"class":114},[32,3436,3437],{"class":124},", retrying...\"",[32,3439,339],{"class":86},[32,3441,3442,3444,3446,3448,3450],{"class":34,"line":72},[32,3443,2155],{"class":82},[32,3445,2158],{"class":114},[32,3447,111],{"class":86},[32,3449,3237],{"class":124},[32,3451,339],{"class":86},[1346,3453,3454,3455,2520,3457,2520,3459,3461],{},"Now a ",[29,3456,3315],{},[29,3458,3023],{},[29,3460,3026],{}," from a real bug propagates immediately on the first attempt, with its original traceback intact, instead of being disguised as a generic \"all attempts failed\" after three pointless retries.",[1346,3463,3464,3467,3468,3470],{},[1353,3465,3466],{},"The lesson",": a bare (or overly broad) ",[29,3469,1242],{}," inside a retry loop is especially dangerous — it doesn't just hide one bug, it wastes time re-attempting a failure that retrying can never fix, then buries the real error under a misleading top-level message.",[14,3472,3474],{"id":3473},"key-takeaways","Key Takeaways",[2937,3476,3477,3499,3513,3519,3522,3528],{},[2940,3478,3479,2520,3481,2520,3483,2520,3485,3487,3488,3490,3491,3493,3494,2520,3496,3498],{},[29,3480,1219],{},[29,3482,1242],{},[29,3484,2986],{},[29,3486,2523],{}," each have distinct roles: ",[29,3489,2986],{}," runs only on success (keeping it separate from error handling), ",[29,3492,2523],{}," always runs, even through ",[29,3495,3091],{},[29,3497,3094],{},"\u002Fan uncaught exception.",[2940,3500,3501,3502,3504,3505,143,3507,3509,3510,3512],{},"Never use a bare ",[29,3503,1528],{}," — it catches typos and programming errors (",[29,3506,3023],{},[29,3508,3026],{},") alongside real, expected failure modes, hiding bugs as silent ",[29,3511,1676],{}," returns or generic messages.",[2940,3514,3515,3516,3518],{},"Order ",[29,3517,1242],{}," clauses from most specific to most general — a broad type listed first makes narrower clauses below it permanently unreachable, with no warning.",[2940,3520,3521],{},"Define a base custom exception per application\u002Flibrary and derive specific exceptions from it, optionally carrying structured data (not just a message) so callers can react programmatically.",[2940,3523,3524,3525,3527],{},"Use ",[29,3526,2111],{}," when deliberately translating one exception into another — it preserves the original cause in the traceback instead of implying an unrelated, accidental second failure.",[2940,3529,3530,3532,3533,3536],{},[29,3531,2847],{}," is stripped entirely under ",[29,3534,3535],{},"python -O"," — never use it for validation that must always run; raise real exceptions for anything representing invalid input or state.",[3538,3539,3540],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":27,"searchDepth":42,"depth":42,"links":3542},[3543,3544,3547,3549,3550,3554,3555,3557,3559,3561,3562,3563,3564],{"id":16,"depth":42,"text":17},{"id":1206,"depth":42,"text":1207,"children":3545},[3546],{"id":1436,"depth":48,"text":1437},{"id":1524,"depth":42,"text":3548},"Never Use a Bare except:",{"id":1680,"depth":42,"text":1681},{"id":1953,"depth":42,"text":3551,"children":3552},"raise ... from — Exception Chaining",[3553],{"id":2115,"depth":48,"text":2116},{"id":2189,"depth":42,"text":2190},{"id":2516,"depth":42,"text":3556},"Context Managers for Cleanup: try\u002Ffinally vs with",{"id":2617,"depth":42,"text":3558},"Multiple except Types and Exception Groups",{"id":2844,"depth":42,"text":3560},"assert Is Not Error Handling",{"id":2934,"depth":42,"text":2935},{"id":3007,"depth":42,"text":3008},{"id":3134,"depth":42,"text":3135},{"id":3473,"depth":42,"text":3474},"md",{},"\u002Fpython\u002F16-error-handling",{"title":5,"description":27},"python\u002F16-error-handling","_cmlh4dE8CmfQPpfRnrQQvQdXM9udGmz4FwPK2KDo6I",1789924651625]