[{"data":1,"prerenderedAt":976},["ShallowReactive",2],{"page-\u002Fsystematic-debugging-performance-profiling\u002Freading-tracebacks-and-exception-chains\u002Fdecoding-during-handling-of-the-above-exception\u002F":3},{"id":4,"title":5,"body":6,"description":942,"extension":943,"meta":944,"navigation":95,"path":972,"seo":973,"stem":974,"__hash__":975},"content\u002Fsystematic-debugging-performance-profiling\u002Freading-tracebacks-and-exception-chains\u002Fdecoding-during-handling-of-the-above-exception\u002Findex.md","Decoding \"During Handling of the Above Exception\"",{"type":7,"value":8,"toc":931},"minimark",[9,34,42,47,63,67,194,202,337,341,373,385,404,408,419,433,452,538,542,545,576,596,610,628,708,712,721,761,771,775,846,850,864,877,889,893,922,927],[10,11,12,13,17,18,21,22,25,26,29,30,33],"p",{},"Python prints two different sentences between chained exceptions, and they mean different things. ",[14,15,16],"code",{},"The above exception was the direct cause of the following exception:"," says someone deliberately converted one error into another with ",[14,19,20],{},"raise NewError(...) from err",". ",[14,23,24],{},"During handling of the above exception, another exception occurred:"," says an exception escaped from an ",[14,27,28],{},"except"," or ",[14,31,32],{},"finally"," block while the first one was still being handled — which is often not deliberate at all, and frequently means the error handler has its own bug.",[10,35,36,37,41],{},"Reading the two correctly changes where you look. A direct-cause chain usually has its root cause at the top, with the wrapper at the bottom describing it at a higher level. A during-handling chain often has ",[38,39,40],"em",{},"two"," problems: the original failure at the top, and a second failure in the code that was supposed to deal with it at the bottom. Fixing only the bottom one leaves the original error unhandled; fixing only the top one leaves a fragile handler that will break again next time.",[43,44,46],"h2",{"id":45},"prerequisites","Prerequisites",[48,49,50,54],"ul",{},[51,52,53],"li",{},"Python 3.11 or later (for fine-grained error locations and exception notes).",[51,55,56,57,62],{},"Familiarity with ",[58,59,61],"a",{"href":60},"\u002Fsystematic-debugging-performance-profiling\u002Freading-tracebacks-and-exception-chains\u002F","Reading tracebacks and exception chains",".",[43,64,66],{"id":65},"solution","Solution",[68,69,74],"pre",{"className":70,"code":71,"language":72,"meta":73,"style":73},"language-python shiki shiki-themes github-light github-dark","# The three chaining forms side by side.\nimport json\n\ndef load_config_implicit(path):\n    try:\n        return json.load(open(path))\n    except json.JSONDecodeError:\n        log.error(\"bad config at %s\", pth)          # typo → NameError inside handler\n\ndef load_config_explicit(path):\n    try:\n        return json.load(open(path))\n    except json.JSONDecodeError as err:\n        raise ConfigError(f\"invalid JSON in {path}\") from err\n\ndef lookup_plan(plans, name):\n    try:\n        return plans[name]\n    except KeyError:\n        raise UnknownPlan(name) from None            # KeyError is noise for the caller\n","python","",[14,75,76,84,90,97,103,109,115,121,127,132,138,143,148,154,160,165,171,176,182,188],{"__ignoreMap":73},[77,78,81],"span",{"class":79,"line":80},"line",1,[77,82,83],{},"# The three chaining forms side by side.\n",[77,85,87],{"class":79,"line":86},2,[77,88,89],{},"import json\n",[77,91,93],{"class":79,"line":92},3,[77,94,96],{"emptyLinePlaceholder":95},true,"\n",[77,98,100],{"class":79,"line":99},4,[77,101,102],{},"def load_config_implicit(path):\n",[77,104,106],{"class":79,"line":105},5,[77,107,108],{},"    try:\n",[77,110,112],{"class":79,"line":111},6,[77,113,114],{},"        return json.load(open(path))\n",[77,116,118],{"class":79,"line":117},7,[77,119,120],{},"    except json.JSONDecodeError:\n",[77,122,124],{"class":79,"line":123},8,[77,125,126],{},"        log.error(\"bad config at %s\", pth)          # typo → NameError inside handler\n",[77,128,130],{"class":79,"line":129},9,[77,131,96],{"emptyLinePlaceholder":95},[77,133,135],{"class":79,"line":134},10,[77,136,137],{},"def load_config_explicit(path):\n",[77,139,141],{"class":79,"line":140},11,[77,142,108],{},[77,144,146],{"class":79,"line":145},12,[77,147,114],{},[77,149,151],{"class":79,"line":150},13,[77,152,153],{},"    except json.JSONDecodeError as err:\n",[77,155,157],{"class":79,"line":156},14,[77,158,159],{},"        raise ConfigError(f\"invalid JSON in {path}\") from err\n",[77,161,163],{"class":79,"line":162},15,[77,164,96],{"emptyLinePlaceholder":95},[77,166,168],{"class":79,"line":167},16,[77,169,170],{},"def lookup_plan(plans, name):\n",[77,172,174],{"class":79,"line":173},17,[77,175,108],{},[77,177,179],{"class":79,"line":178},18,[77,180,181],{},"        return plans[name]\n",[77,183,185],{"class":79,"line":184},19,[77,186,187],{},"    except KeyError:\n",[77,189,191],{"class":79,"line":190},20,[77,192,193],{},"        raise UnknownPlan(name) from None            # KeyError is noise for the caller\n",[68,195,200],{"className":196,"code":198,"language":199,"meta":73},[197],"language-text","Traceback (most recent call last):\n  File \"config.py\", line 5, in load_config_implicit\n    return json.load(open(path))\njson.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"config.py\", line 7, in load_config_implicit\n    log.error(\"bad config at %s\", pth)\n                                  ^^^\nNameError: name 'pth' is not defined. Did you mean: 'path'?\n","text",[14,201,198],{"__ignoreMap":73},[203,204,207,333],"figure",{"className":205},[206],"diagram",[208,209,216,217,216,221,216,225,216,233,216,242,216,252,216,258,216,264,216,268,216,274,216,278,216,282,216,287,216,290,216,294,216,297,216,301,216,304,216,307,216,312,216,316,216,320,216,323,216,327,216,330],"svg",{"viewBox":210,"role":211,"ariaLabelledBy":212,"xmlns":215},"0 0 800 256","img",[213,214],"ec-t","ec-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[218,219,220],"title",{"id":213},"Implicit and explicit exception chaining",[222,223,224],"desc",{"id":214},"Three columns show chaining forms. Implicit chaining happens when an exception escapes an except block; Python sets __context__ and prints the during-handling message. Explicit chaining with raise from err sets __cause__ and prints the direct-cause message. raise from None sets __suppress_context__ so only the new exception is printed.",[226,227],"rect",{"x":228,"y":228,"width":229,"height":230,"rx":231,"fill":232},"0","800","256","14","#fffdf8",[199,234,241],{"x":235,"y":236,"textAnchor":237,"fontSize":238,"fontWeight":239,"fill":240},"400","28","middle","15.5","700","#3d405b","Three ways one exception follows another",[226,243],{"x":244,"y":245,"width":246,"height":247,"rx":248,"fill":249,"stroke":250,"strokeWidth":251},"26","50","236","184","12","#fbe9e3","#e07a5f","2",[199,253,257],{"x":254,"y":255,"textAnchor":237,"fontSize":256,"fontWeight":239,"fill":240},"144","78","12.5","implicit",[199,259,263],{"x":260,"y":261,"fontSize":262,"fill":240},"44","106","11","error escapes except\u002Ffinally",[199,265,267],{"x":260,"y":266,"fontSize":262,"fill":240},"128","sets __context__",[199,269,273],{"x":260,"y":270,"fontSize":271,"fill":272},"164","10.5","#8f3d22","\"During handling of the",[199,275,277],{"x":260,"y":276,"fontSize":271,"fill":272},"182","above exception…\"",[199,279,281],{"x":260,"y":280,"fontSize":262,"fontWeight":239,"fill":272},"214","often a handler bug",[226,283],{"x":284,"y":245,"width":246,"height":247,"rx":248,"fill":285,"stroke":286,"strokeWidth":251},"282","#e6f0ea","#81b29a",[199,288,289],{"x":235,"y":255,"textAnchor":237,"fontSize":256,"fontWeight":239,"fill":240},"raise … from err",[199,291,293],{"x":292,"y":261,"fontSize":262,"fill":240},"300","deliberate wrapping",[199,295,296],{"x":292,"y":266,"fontSize":262,"fill":240},"sets __cause__",[199,298,300],{"x":292,"y":270,"fontSize":271,"fill":299},"#2a5f49","\"The above exception was",[199,302,303],{"x":292,"y":276,"fontSize":271,"fill":299},"the direct cause…\"",[199,305,306],{"x":292,"y":280,"fontSize":262,"fontWeight":239,"fill":299},"intended translation",[226,308],{"x":309,"y":245,"width":246,"height":247,"rx":248,"fill":310,"stroke":311,"strokeWidth":251},"538","#f7f0da","#f2cc8f",[199,313,315],{"x":314,"y":255,"textAnchor":237,"fontSize":256,"fontWeight":239,"fill":240},"656","raise … from None",[199,317,319],{"x":318,"y":261,"fontSize":262,"fill":240},"556","hide the internal error",[199,321,322],{"x":318,"y":266,"fontSize":262,"fill":240},"__suppress_context__ = True",[199,324,326],{"x":318,"y":270,"fontSize":271,"fill":325},"#8a5a00","only the new exception",[199,328,329],{"x":318,"y":276,"fontSize":271,"fill":325},"is printed",[199,331,332],{"x":318,"y":280,"fontSize":262,"fontWeight":239,"fill":325},"context still on the object",[334,335,336],"figcaption",{},"The separator sentence tells you which column you are in — and therefore whether the bottom exception was intended.",[43,338,340],{"id":339},"why-this-works","Why this works",[10,342,343,344,347,348,350,351,353,354,357,358,21,361,364,365,368,369,372],{},"Every exception object carries three attributes that describe its relationship to others. ",[14,345,346],{},"__context__"," is set automatically whenever an exception is raised while another is being handled — inside an ",[14,349,28],{}," block, or inside a ",[14,352,32],{}," block during unwinding. ",[14,355,356],{},"__cause__"," is set only by ",[14,359,360],{},"raise X from Y",[14,362,363],{},"__suppress_context__"," is set to true by any ",[14,366,367],{},"raise ... from ...",", including ",[14,370,371],{},"from None",", and tells the traceback printer to skip the implicit context.",[10,374,375,376,378,379,381,382,384],{},"The traceback printer walks the chain from the propagated exception backwards and prints oldest first. If ",[14,377,356],{}," is set, it prints the cause with the direct-cause separator. Otherwise, if ",[14,380,346],{}," is set and not suppressed, it prints the context with the during-handling separator. That is why the order on screen runs from the first failure at the top to the propagated one at the bottom: the bottom exception is the one your ",[14,383,28],{}," clauses — or pytest — actually saw.",[10,386,387,388,391,392,395,396,399,400,403],{},"In the example, the program failed with ",[14,389,390],{},"NameError",", not ",[14,393,394],{},"JSONDecodeError",". A caller with ",[14,397,398],{},"except JSONDecodeError"," would not catch it. That is the practical danger of implicit chains: a bug in a handler changes the ",[38,401,402],{},"type"," of the error that escapes, and code written to handle the original type silently stops working.",[43,405,407],{"id":406},"reading-chains-inside-pytest","Reading chains inside pytest",[10,409,410,411,414,415,418],{},"pytest prints chains the same way, with its own formatting. With the default ",[14,412,413],{},"--tb=auto",", each exception in the chain gets its own section, separated by the same sentences, and the final section carries the ",[14,416,417],{},"E"," lines of the propagated exception. Two things are worth knowing.",[10,420,421,422,425,426,429,430,432],{},"First, ",[14,423,424],{},"pytest.raises"," matches the propagated exception only. ",[14,427,428],{},"with pytest.raises(JSONDecodeError)"," fails on the implicit chain above, because what escaped was a ",[14,431,390],{},". If a test that used to pass starts failing that way, read the chain: the handler probably broke.",[10,434,435,436,439,440,443,444,447,448,451],{},"Second, ",[14,437,438],{},"--tb=short"," and ",[14,441,442],{},"--tb=line"," shorten or drop the chain entirely. When a failure is confusing, re-run with ",[14,445,446],{},"--tb=long"," to see every frame of every exception, or use ",[14,449,450],{},"--full-trace"," to include pytest's own frames as well.",[203,453,455,535],{"className":454},[206],[208,456,216,461,216,464,216,467,216,471,216,475,216,484,216,489,216,492,216,498,216,502,216,505,216,509,216,513,216,519,216,523,216,526,216,528,216,531],{"viewBox":457,"role":211,"ariaLabelledBy":458,"xmlns":215},"90 0 580 236",[459,460],"ecr-t","ecr-d",[218,462,463],{"id":459},"Reading order for a chained traceback",[222,465,466],{"id":460},"A chained traceback is shown as two stacked blocks. The bottom block is the propagated exception and is read first because it is what callers and pytest saw. The top block is the original exception and is read second to find the root cause. The separator between them tells whether the chain was deliberate.",[226,468],{"x":469,"y":228,"width":470,"height":246,"rx":231,"fill":232},"90","580",[199,472,474],{"x":473,"y":236,"textAnchor":237,"fontSize":238,"fontWeight":239,"fill":240},"380","Read bottom, then separator, then top",[226,476],{"x":477,"y":478,"width":479,"height":480,"rx":481,"fill":482,"stroke":240,"strokeWidth":483},"120","48","440","52","10","#f4f1de","1.5",[199,485,488],{"x":486,"y":487,"textAnchor":237,"fontSize":248,"fontWeight":239,"fill":240},"340","72","original exception",[199,490,491],{"x":486,"y":469,"textAnchor":237,"fontSize":271,"fill":240},"root cause lives here",[226,493],{"x":477,"y":494,"width":479,"height":495,"rx":496,"fill":310,"stroke":311,"strokeWidth":497},"108","34","8","1.6",[199,499,501],{"x":486,"y":500,"textAnchor":237,"fontSize":262,"fill":325},"130","separator: deliberate or accidental?",[226,503],{"x":477,"y":504,"width":479,"height":480,"rx":481,"fill":249,"stroke":250,"strokeWidth":251},"150",[199,506,508],{"x":486,"y":507,"textAnchor":237,"fontSize":248,"fontWeight":239,"fill":240},"174","propagated exception",[199,510,512],{"x":486,"y":511,"textAnchor":237,"fontSize":271,"fill":240},"192","what callers and pytest saw",[514,515],"circle",{"cx":516,"cy":517,"r":518,"fill":240},"620","176","15",[199,520,522],{"x":516,"y":521,"textAnchor":237,"fontSize":248,"fontWeight":239,"fill":232},"181","1",[514,524],{"cx":516,"cy":525,"r":518,"fill":240},"125",[199,527,251],{"x":516,"y":500,"textAnchor":237,"fontSize":248,"fontWeight":239,"fill":232},[514,529],{"cx":516,"cy":530,"r":518,"fill":240},"74",[199,532,534],{"x":516,"y":533,"textAnchor":237,"fontSize":248,"fontWeight":239,"fill":232},"79","3",[334,536,537],{},"Start from what escaped, decide whether the chain was intended, then go up to the original failure.",[43,539,541],{"id":540},"writing-handlers-that-chain-on-purpose","Writing handlers that chain on purpose",[10,543,544],{},"Most implicit chains are accidents, and the fix is to make every handler's intent explicit. Three rules cover nearly every case.",[10,546,547,554,555,557,558,561,562,557,565,568,569,571,572,575],{},[548,549,550,551,62],"strong",{},"Translate with ",[14,552,553],{},"from err"," When a lower layer's exception should become a higher layer's error — a ",[14,556,394],{}," becoming a ",[14,559,560],{},"ConfigError",", a database ",[14,563,564],{},"IntegrityError",[14,566,567],{},"DuplicateUser"," — always write ",[14,570,20],{},". The traceback then says \"direct cause\", which tells the next reader the translation was deliberate, and ",[14,573,574],{},"err.__cause__"," gives programmatic access to the original for logging or retry decisions.",[10,577,578,584,585,588,589,591,592,595],{},[548,579,580,581,583],{},"Hide with ",[14,582,371],{},", sparingly."," When the lower-level exception is an implementation detail that adds nothing — an internal ",[14,586,587],{},"KeyError"," from a dict lookup that the caller only knows as \"unknown plan\" — ",[14,590,371],{}," keeps the caller's traceback short. Do this only where the original genuinely has no diagnostic value; hiding an ",[14,593,594],{},"OSError"," with an errno is a false economy.",[10,597,598,601,602,605,606,609],{},[548,599,600],{},"Keep handlers boring."," The during-handling message usually appears because a handler did something that could fail: formatted a message with a misspelled variable, called a cleanup function that raised, logged an object whose ",[14,603,604],{},"__repr__"," raised. Handlers should do as little as possible, and anything risky inside them — closing a connection, sending a metric — should be wrapped in its own ",[14,607,608],{},"try"," so a failure there cannot replace the original error.",[10,611,612,613,616,617,620,621,624,625,627],{},"Python 3.11's ",[14,614,615],{},"add_note"," offers a fourth option that avoids chaining altogether. Instead of wrapping an exception to add context, add a note and re-raise the original: ",[14,618,619],{},"err.add_note(f\"while loading {path}\")"," then ",[14,622,623],{},"raise",". The traceback shows the original exception with the note underneath, the type is unchanged, and callers' ",[14,626,28],{}," clauses keep working.",[203,629,631,705],{"className":630},[206],[208,632,216,637,216,640,216,643,216,645,216,648,216,652,216,658,216,662,216,665,216,668,216,672,216,675,216,678,216,681,216,685,216,688,216,691,216,695,216,699,216,702],{"viewBox":633,"role":211,"ariaLabelledBy":634,"xmlns":215},"0 0 800 236",[635,636],"ech-t","ech-d",[218,638,639],{"id":635},"Choosing how a handler should raise",[222,641,642],{"id":636},"A decision flow for handlers. If the caller should see a different error type, raise the new error from the original. If the original is pure noise, raise from None. If you only want to add context, add a note and re-raise the original unchanged. Risky work inside the handler gets its own try block.",[226,644],{"x":228,"y":228,"width":229,"height":246,"rx":231,"fill":232},[199,646,647],{"x":235,"y":236,"textAnchor":237,"fontSize":238,"fontWeight":239,"fill":240},"Make every handler's intent explicit",[226,649],{"x":244,"y":480,"width":517,"height":650,"rx":262,"fill":285,"stroke":286,"strokeWidth":651},"160","1.8",[199,653,657],{"x":654,"y":655,"textAnchor":237,"fontSize":656,"fontWeight":239,"fill":240},"114","80","11.5","new error type",[199,659,661],{"x":654,"y":660,"textAnchor":237,"fontSize":271,"fill":299},"118","raise New() from err",[199,663,664],{"x":654,"y":504,"textAnchor":237,"fontSize":271,"fill":240},"direct cause shown",[226,666],{"x":667,"y":480,"width":517,"height":650,"rx":262,"fill":310,"stroke":311,"strokeWidth":651},"216",[199,669,671],{"x":670,"y":655,"textAnchor":237,"fontSize":656,"fontWeight":239,"fill":240},"304","original is noise",[199,673,674],{"x":670,"y":660,"textAnchor":237,"fontSize":271,"fill":325},"raise New() from None",[199,676,677],{"x":670,"y":504,"textAnchor":237,"fontSize":271,"fill":240},"short traceback",[226,679],{"x":680,"y":480,"width":517,"height":650,"rx":262,"fill":482,"stroke":240,"strokeWidth":483},"406",[199,682,684],{"x":683,"y":655,"textAnchor":237,"fontSize":656,"fontWeight":239,"fill":240},"494","just add context",[199,686,687],{"x":683,"y":660,"textAnchor":237,"fontSize":271,"fill":240},"err.add_note(…); raise",[199,689,690],{"x":683,"y":504,"textAnchor":237,"fontSize":271,"fill":240},"type unchanged",[226,692],{"x":693,"y":480,"width":694,"height":650,"rx":262,"fill":249,"stroke":250,"strokeWidth":651},"596","178",[199,696,698],{"x":697,"y":655,"textAnchor":237,"fontSize":656,"fontWeight":239,"fill":240},"685","risky cleanup",[199,700,701],{"x":697,"y":660,"textAnchor":237,"fontSize":271,"fill":272},"own try\u002Fexcept inside",[199,703,704],{"x":697,"y":504,"textAnchor":237,"fontSize":271,"fill":240},"can't replace the error",[334,706,707],{},"With these four habits, a \"during handling\" message in your own code becomes rare enough to be a reliable signal of a real handler bug.",[43,709,711],{"id":710},"testing-the-chain-itself","Testing the chain itself",[10,713,714,715,717,718,720],{},"When a function's contract says it raises ",[14,716,560],{}," for invalid files, the chain is part of that contract too: callers may log ",[14,719,574],{}," or branch on it. A small test pins the behaviour so a future refactor cannot quietly turn a deliberate translation into an accidental one.",[68,722,724],{"className":70,"code":723,"language":72,"meta":73,"style":73},"def test_invalid_json_is_translated(tmp_path):\n    bad = tmp_path \u002F \"c.json\"\n    bad.write_text(\"{not json\")\n    with pytest.raises(ConfigError) as info:\n        load_config_explicit(bad)\n    assert isinstance(info.value.__cause__, json.JSONDecodeError)\n    assert \"c.json\" in str(info.value)\n",[14,725,726,731,736,741,746,751,756],{"__ignoreMap":73},[77,727,728],{"class":79,"line":80},[77,729,730],{},"def test_invalid_json_is_translated(tmp_path):\n",[77,732,733],{"class":79,"line":86},[77,734,735],{},"    bad = tmp_path \u002F \"c.json\"\n",[77,737,738],{"class":79,"line":92},[77,739,740],{},"    bad.write_text(\"{not json\")\n",[77,742,743],{"class":79,"line":99},[77,744,745],{},"    with pytest.raises(ConfigError) as info:\n",[77,747,748],{"class":79,"line":105},[77,749,750],{},"        load_config_explicit(bad)\n",[77,752,753],{"class":79,"line":111},[77,754,755],{},"    assert isinstance(info.value.__cause__, json.JSONDecodeError)\n",[77,757,758],{"class":79,"line":117},[77,759,760],{},"    assert \"c.json\" in str(info.value)\n",[10,762,763,764,767,768,770],{},"The first assertion checks the type callers rely on. The second checks that the cause is the original parsing error rather than something a buggy handler raised. The third checks the message is useful. A test like this would have caught the misspelled variable in the implicit example immediately: ",[14,765,766],{},"pytest.raises(ConfigError)"," fails when what escapes is a ",[14,769,390],{},", and the chain printed in the failure shows exactly why. Tests of this shape are cheap to write for every public function that translates errors, and they turn the chain from an accident of implementation into a documented, enforced part of the interface that reviewers can see.",[43,772,774],{"id":773},"edge-cases-and-failure-modes","Edge cases and failure modes",[48,776,777,798,810,823,833],{},[51,778,779,784,785,787,788,790,791,794,795,797],{},[548,780,781,783],{},[14,782,32],{}," blocks."," An exception in ",[14,786,32],{}," during unwinding chains implicitly, like one in ",[14,789,28],{},". A ",[14,792,793],{},"return"," in ",[14,796,32],{}," swallows the original exception entirely — Python 3.14 warns about it.",[51,799,800,806,807,809],{},[548,801,802,803,62],{},"Re-raising with ",[14,804,805],{},"raise err"," Re-raising the same object inside its own handler does not chain; ",[14,808,623],{}," alone re-raises with the original traceback, which is usually what you want.",[51,811,812,818,819,822],{},[548,813,814,815,62],{},"Logging with ",[14,816,817],{},"exc_info=True"," The ",[14,820,821],{},"logging"," module prints the full chain for the current exception. Logging inside a handler that then raises something else logs the first and propagates the second.",[51,824,825,828,829,832],{},[548,826,827],{},"Exception groups."," ",[14,830,831],{},"ExceptionGroup"," tracebacks nest sub-exceptions with box-drawing characters; each sub-exception can itself be chained. Read each branch the same way.",[51,834,835,838,839,841,842,845],{},[548,836,837],{},"Library wrappers hiding causes."," Some libraries use ",[14,840,371],{}," liberally. When a wrapper hides something you need, inspect ",[14,843,844],{},"err.__context__"," in a debugger — it is still there.",[43,847,849],{"id":848},"frequently-asked-questions","Frequently Asked Questions",[10,851,852,855,856,29,858,860,861,863],{},[548,853,854],{},"What does \"During handling of the above exception, another exception occurred\" mean?","\nAn exception was raised inside an ",[14,857,28],{},[14,859,32],{}," block while the first exception was being handled. Python records the first one as the second's ",[14,862,346],{}," and prints both, first one on top. The second exception is the one that propagated.",[10,865,866,869,870,873,874,876],{},[548,867,868],{},"What is the difference between that message and \"The above exception was the direct cause\"?","\nThe direct-cause message comes from ",[14,871,872],{},"raise ... from err",", which sets ",[14,875,356],{}," and says the new exception deliberately wraps the old. The during-handling message comes from implicit chaining and often signals a bug in the handler itself.",[10,878,879,882,883,885,886,888],{},[548,880,881],{},"When should I use raise from None?","\nWhen the original exception is an implementation detail that would only confuse the caller, such as a ",[14,884,587],{}," from an internal lookup translated into a domain error. It suppresses the context in the printed traceback while keeping it on ",[14,887,346],{}," for debugging.",[43,890,892],{"id":891},"related","Related",[48,894,895,901,908,915],{},[51,896,897,900],{},[58,898,899],{"href":60},"Reading Tracebacks & Exception Chains"," — frame order, locations and tb styles.",[51,902,903,907],{},[58,904,906],{"href":905},"\u002Fsystematic-debugging-performance-profiling\u002Freading-tracebacks-and-exception-chains\u002Fgetting-useful-tracebacks-from-threads-and-tasks\u002F","Getting Useful Tracebacks from Threads and Tasks"," — chains that cross thread boundaries.",[51,909,910,914],{},[58,911,913],{"href":912},"\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002Fpost-mortem-debugging-with-pdb-pm\u002F","Post-Mortem Debugging with pdb.pm()"," — inspecting each link interactively.",[51,916,917,921],{},[58,918,920],{"href":919},"\u002Fsystematic-debugging-performance-profiling\u002Flogging-and-observability-for-debugging\u002Fstructured-logging-that-survives-pytest-capture\u002F","Structured Logging That Survives pytest Capture"," — logging chained errors.",[10,923,924,925],{},"← Back to ",[58,926,899],{"href":60},[928,929,930],"style",{},"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);}",{"title":73,"searchDepth":86,"depth":86,"links":932},[933,934,935,936,937,938,939,940,941],{"id":45,"depth":86,"text":46},{"id":65,"depth":86,"text":66},{"id":339,"depth":86,"text":340},{"id":406,"depth":86,"text":407},{"id":540,"depth":86,"text":541},{"id":710,"depth":86,"text":711},{"id":773,"depth":86,"text":774},{"id":848,"depth":86,"text":849},{"id":891,"depth":86,"text":892},"Read Python's chained tracebacks correctly: implicit __context__ versus explicit __cause__, which exception is the real failure, raise from None, and chaining in pytest output.","md",{"slug":945,"type":946,"breadcrumb":947,"datePublished":948,"dateModified":948,"faq":949,"howto":956},"decoding-during-handling-of-the-above-exception","article","Exception chaining","2026-09-18",[950,952,954],{"q":854,"a":951},"An exception was raised inside an except or finally block while the first exception was being handled. Python records the first one as the second's __context__ and prints both, first one on top. The second exception is the one that propagated.",{"q":868,"a":953},"The direct-cause message comes from raise ... from err, which sets __cause__ and says the new exception deliberately wraps the old. The during-handling message comes from implicit chaining and often signals a bug in the handler itself.",{"q":881,"a":955},"When the original exception is an implementation detail that would only confuse the caller, such as a KeyError from an internal lookup translated into a domain error. It suppresses the context in the printed traceback while keeping it on __context__ for debugging.",{"name":957,"description":958,"steps":959},"How to read a chained Python traceback","Identify the chaining type, find the propagated exception, and trace the original cause.",[960,963,966,969],{"name":961,"text":962},"Read the bottom exception first","The last exception printed is the one that propagated and failed the program or test.",{"name":964,"text":965},"Check the separator line","Direct cause means deliberate wrapping; during handling means the handler itself failed.",{"name":967,"text":968},"Inspect the handler for implicit chains","Look at the except or finally block where the second exception was raised.",{"name":970,"text":971},"Make intent explicit","Use raise ... from err or from None so future tracebacks say what was intended.","\u002Fsystematic-debugging-performance-profiling\u002Freading-tracebacks-and-exception-chains\u002Fdecoding-during-handling-of-the-above-exception",{"title":5,"description":942},"systematic-debugging-performance-profiling\u002Freading-tracebacks-and-exception-chains\u002Fdecoding-during-handling-of-the-above-exception\u002Findex","a2GdMyUtS8vPaN9sGydK3p-NGPO33OPESqWXSBqW7yQ",1789718769347]