[{"data":1,"prerenderedAt":629},["ShallowReactive",2],{"page-\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002Fpost-mortem-debugging-with-pdb-pm\u002F":3},{"id":4,"title":5,"body":6,"description":595,"extension":596,"meta":597,"navigation":135,"path":625,"seo":626,"stem":627,"__hash__":628},"content\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002Fpost-mortem-debugging-with-pdb-pm\u002Findex.md","Post-Mortem Debugging with pdb.pm()",{"type":7,"value":8,"toc":588},"minimark",[9,23,28,80,84,107,171,213,219,290,297,352,365,385,391,410,433,437,456,460,526,530,548,565,578,584],[10,11,12,13,17,18,22],"p",{},"A batch job crashes once at 3 a.m. with a ",[14,15,16],"code",{},"KeyError"," deep in a call stack, and you cannot reproduce it interactively. Re-running with a breakpoint is hopeless if the input is gone. Post-mortem debugging reopens the ",[19,20,21],"em",{},"exact frame"," where the exception was raised, with every local still intact, so you inspect the state at the moment of failure rather than guessing from a traceback.",[24,25,27],"h2",{"id":26},"prerequisites","Prerequisites",[29,30,31,51,61,71],"ul",{},[32,33,34,38,39,42,43,46,47,50],"li",{},[35,36,37],"strong",{},"Python 3.x"," — ",[14,40,41],{},"pdb.post_mortem",", ",[14,44,45],{},"pdb.pm",", and the ",[14,48,49],{},"sys.last_*"," attributes are long-standing standard-library features.",[32,52,53,56,57,60],{},[35,54,55],{},"pytest 5.4+"," for ",[14,58,59],{},"--pdb"," post-mortem on test failure.",[32,62,63,66,67,70],{},[35,64,65],{},"IPython"," if you want the ",[14,68,69],{},"%debug"," magic; the underlying mechanism is identical.",[32,72,73,74,79],{},"The command vocabulary from ",[75,76,78],"a",{"href":77},"\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002F","interactive debugging with pdb and ipdb"," — post-mortem drops you into the same prompt.",[24,81,83],{"id":82},"solution","Solution",[10,85,86,87,90,91,94,95,98,99,102,103,106],{},"When an unhandled exception reaches the top level, the interpreter stores its traceback on ",[14,88,89],{},"sys.last_traceback"," (with ",[14,92,93],{},"sys.last_value"," and ",[14,96,97],{},"sys.last_type","). ",[14,100,101],{},"pdb.pm()"," opens a post-mortem session on that stored traceback; ",[14,104,105],{},"pdb.post_mortem(tb)"," does the same for any traceback object you hand it.",[108,109,114],"pre",{"className":110,"code":111,"language":112,"meta":113,"style":113},"language-python shiki shiki-themes github-light github-dark","# In an interactive session or REPL after a crash:\nimport pdb\n\ndef load(config):\n    return config[\"timeout\"]      # KeyError if the key is missing\n\nload({})                          # raises KeyError: 'timeout', prints a traceback\n\npdb.pm()                          # reopen the failing frame from sys.last_traceback\n","python","",[14,115,116,124,130,137,143,149,154,160,165],{"__ignoreMap":113},[117,118,121],"span",{"class":119,"line":120},"line",1,[117,122,123],{},"# In an interactive session or REPL after a crash:\n",[117,125,127],{"class":119,"line":126},2,[117,128,129],{},"import pdb\n",[117,131,133],{"class":119,"line":132},3,[117,134,136],{"emptyLinePlaceholder":135},true,"\n",[117,138,140],{"class":119,"line":139},4,[117,141,142],{},"def load(config):\n",[117,144,146],{"class":119,"line":145},5,[117,147,148],{},"    return config[\"timeout\"]      # KeyError if the key is missing\n",[117,150,152],{"class":119,"line":151},6,[117,153,136],{"emptyLinePlaceholder":135},[117,155,157],{"class":119,"line":156},7,[117,158,159],{},"load({})                          # raises KeyError: 'timeout', prints a traceback\n",[117,161,163],{"class":119,"line":162},8,[117,164,136],{"emptyLinePlaceholder":135},[117,166,168],{"class":119,"line":167},9,[117,169,170],{},"pdb.pm()                          # reopen the failing frame from sys.last_traceback\n",[108,172,176],{"className":173,"code":174,"language":175,"meta":113,"style":113},"language-console shiki shiki-themes github-light github-dark","> example.py(4)load()\n-> return config[\"timeout\"]\n(Pdb) p config        # the exact argument that caused the crash, still alive\n{}\n(Pdb) up              # walk toward the caller if needed\n(Pdb) p sys.last_value\nKeyError('timeout')\n","console",[14,177,178,183,188,193,198,203,208],{"__ignoreMap":113},[117,179,180],{"class":119,"line":120},[117,181,182],{},"> example.py(4)load()\n",[117,184,185],{"class":119,"line":126},[117,186,187],{},"-> return config[\"timeout\"]\n",[117,189,190],{"class":119,"line":132},[117,191,192],{},"(Pdb) p config        # the exact argument that caused the crash, still alive\n",[117,194,195],{"class":119,"line":139},[117,196,197],{},"{}\n",[117,199,200],{"class":119,"line":145},[117,201,202],{},"(Pdb) up              # walk toward the caller if needed\n",[117,204,205],{"class":119,"line":151},[117,206,207],{},"(Pdb) p sys.last_value\n",[117,209,210],{"class":119,"line":156},[117,211,212],{},"KeyError('timeout')\n",[10,214,215,216,218],{},"When you catch the exception yourself, grab the traceback off the exception object and pass it explicitly — this works inside a script where ",[14,217,89],{}," is never set:",[108,220,222],{"className":110,"code":221,"language":112,"meta":113,"style":113},"import pdb\nimport sys\n\ndef run():\n    try:\n        risky()\n    except Exception:\n        # __traceback__ holds the frames; post_mortem reopens the deepest one\n        pdb.post_mortem(sys.exc_info()[2])\n\ndef risky():\n    data = [1, 2, 3]\n    return data[99]               # IndexError\n",[14,223,224,228,233,237,242,247,252,257,262,267,272,278,284],{"__ignoreMap":113},[117,225,226],{"class":119,"line":120},[117,227,129],{},[117,229,230],{"class":119,"line":126},[117,231,232],{},"import sys\n",[117,234,235],{"class":119,"line":132},[117,236,136],{"emptyLinePlaceholder":135},[117,238,239],{"class":119,"line":139},[117,240,241],{},"def run():\n",[117,243,244],{"class":119,"line":145},[117,245,246],{},"    try:\n",[117,248,249],{"class":119,"line":151},[117,250,251],{},"        risky()\n",[117,253,254],{"class":119,"line":156},[117,255,256],{},"    except Exception:\n",[117,258,259],{"class":119,"line":162},[117,260,261],{},"        # __traceback__ holds the frames; post_mortem reopens the deepest one\n",[117,263,264],{"class":119,"line":167},[117,265,266],{},"        pdb.post_mortem(sys.exc_info()[2])\n",[117,268,270],{"class":119,"line":269},10,[117,271,136],{"emptyLinePlaceholder":135},[117,273,275],{"class":119,"line":274},11,[117,276,277],{},"def risky():\n",[117,279,281],{"class":119,"line":280},12,[117,282,283],{},"    data = [1, 2, 3]\n",[117,285,287],{"class":119,"line":286},13,[117,288,289],{},"    return data[99]               # IndexError\n",[10,291,292,293,296],{},"For an unattended script, launch it under pdb with ",[14,294,295],{},"-c continue",". The script runs at full speed; only if it crashes does pdb take over at the failing frame:",[108,298,302],{"className":299,"code":300,"language":301,"meta":113,"style":113},"language-bash shiki shiki-themes github-light github-dark","python -m pdb -c continue batch_job.py\n# ... normal output ...\n# Traceback ... then automatically:\n# > batch_job.py(57)transform()\n# -> return row[key]\n# (Pdb)\n","bash",[14,303,304,326,332,337,342,347],{"__ignoreMap":113},[117,305,306,309,313,317,320,323],{"class":119,"line":120},[117,307,112],{"class":308},"sScJk",[117,310,312],{"class":311},"sj4cs"," -m",[117,314,316],{"class":315},"sZZnC"," pdb",[117,318,319],{"class":311}," -c",[117,321,322],{"class":315}," continue",[117,324,325],{"class":315}," batch_job.py\n",[117,327,328],{"class":119,"line":126},[117,329,331],{"class":330},"sJ8bj","# ... normal output ...\n",[117,333,334],{"class":119,"line":132},[117,335,336],{"class":330},"# Traceback ... then automatically:\n",[117,338,339],{"class":119,"line":139},[117,340,341],{"class":330},"# > batch_job.py(57)transform()\n",[117,343,344],{"class":119,"line":145},[117,345,346],{"class":330},"# -> return row[key]\n",[117,348,349],{"class":119,"line":151},[117,350,351],{"class":330},"# (Pdb)\n",[10,353,354,355,357,358,360,361,364],{},"Inside IPython or Jupyter, ",[14,356,69],{}," is the one-liner equivalent of ",[14,359,101],{}," — it opens a post-mortem prompt on the last exception. ",[14,362,363],{},"%pdb on"," arms it so every subsequent uncaught exception drops you in automatically.",[108,366,368],{"className":110,"code":367,"language":112,"meta":113,"style":113},"# IPython\nIn [1]: load({})          # raises KeyError\nIn [2]: %debug            # post-mortem prompt at the failing frame\n",[14,369,370,375,380],{"__ignoreMap":113},[117,371,372],{"class":119,"line":120},[117,373,374],{},"# IPython\n",[117,376,377],{"class":119,"line":126},[117,378,379],{},"In [1]: load({})          # raises KeyError\n",[117,381,382],{"class":119,"line":132},[117,383,384],{},"In [2]: %debug            # post-mortem prompt at the failing frame\n",[10,386,387,388,390],{},"pytest exposes the same behaviour with ",[14,389,59],{},": on any test failure it opens a post-mortem prompt in the failing frame with the assertion's exception live.",[108,392,394],{"className":299,"code":393,"language":301,"meta":113,"style":113},"pytest tests\u002Ftest_config.py --pdb   # drop into post-mortem at the point of failure\n",[14,395,396],{"__ignoreMap":113},[117,397,398,401,404,407],{"class":119,"line":120},[117,399,400],{"class":308},"pytest",[117,402,403],{"class":315}," tests\u002Ftest_config.py",[117,405,406],{"class":311}," --pdb",[117,408,409],{"class":330},"   # drop into post-mortem at the point of failure\n",[10,411,412,413,416,417,419,420,423,424,427,428,432],{},"This is the natural follow-on to ",[14,414,415],{},"--trace"," (break at test start) covered in the parent guide; use ",[14,418,59],{}," when you want to inspect ",[19,421,422],{},"after"," the failure rather than step ",[19,425,426],{},"into"," the test. If the failure is intermittent across runs, pair post-mortem triage with the rerun analysis in ",[75,429,431],{"href":430},"\u002Fadvanced-pytest-architecture-configuration\u002Foptimizing-test-discovery\u002Fdebugging-flaky-tests-with-pytest-rerunfailures\u002F","debugging flaky tests with pytest-rerunfailures",".",[24,434,436],{"id":435},"why-this-works","Why this works",[10,438,439,440,42,442,445,446,449,450,452,453,455],{},"A traceback object is a linked list of frame objects, each carrying its locals and globals at the instant the exception unwound. Post-mortem debugging does not re-execute anything — it points the pdb command loop at the deepest frame in that captured chain, so ",[14,441,10],{},[14,443,444],{},"up",", and ",[14,447,448],{},"down"," read the preserved state. Because the frames are kept alive by the traceback reference, the state survives until that reference is dropped; ",[14,451,101],{}," simply reuses the interpreter's own top-level capture (",[14,454,89],{},").",[24,457,459],{"id":458},"edge-cases-and-failure-modes","Edge cases and failure modes",[29,461,462,479,489,499,508],{},[32,463,464,472,473,475,476,478],{},[35,465,466,468,469],{},[14,467,101],{}," raises ",[14,470,471],{},"AttributeError"," when no unhandled exception has reached the top level — there is no ",[14,474,89],{},". Use ",[14,477,105],{}," with an explicit traceback inside scripts.",[32,480,481,484,485,488],{},[35,482,483],{},"The traceback's frames pin their locals in memory","; holding a traceback reference (or an exception via ",[14,486,487],{},"except ... as e",") for a long time can leak large objects — clear it when done.",[32,490,491,494,495,498],{},[35,492,493],{},"Post-mortem state is read-only in spirit",": you can evaluate expressions, but you cannot resume execution from a post-mortem frame; ",[14,496,497],{},"continue"," simply exits the session.",[32,500,501,507],{},[35,502,503,506],{},[14,504,505],{},"python -m pdb -c continue"," re-runs the program","; if the crash depends on external state that has changed, it may not reproduce.",[32,509,510,513,514,517,518,521,522,525],{},[35,511,512],{},"Chained exceptions"," (",[14,515,516],{},"raise ... from",") land you on the most recent one; walk ",[14,519,520],{},"sys.last_value.__cause__"," or ",[14,523,524],{},"__context__"," to inspect the original.",[24,527,529],{"id":528},"frequently-asked-questions","Frequently Asked Questions",[10,531,532,535,537,538,540,541,544,545,547],{},[35,533,534],{},"What is the difference between pdb.pm() and pdb.post_mortem()?",[14,536,105],{}," starts a post-mortem session on a traceback you pass it. ",[14,539,101],{}," is a convenience wrapper that calls ",[14,542,543],{},"post_mortem"," on ",[14,546,89],{},", the traceback of the most recent unhandled exception stored by the interpreter, so you can debug a crash that already printed.",[10,549,550,553,554,42,556,445,558,560,561,468,563,432],{},[35,551,552],{},"Why is sys.last_traceback sometimes missing?","\nThe interpreter only sets ",[14,555,89],{},[14,557,93],{},[14,559,97],{}," when an unhandled exception reaches the top level in interactive mode. Inside a script that caught the exception, or in a fresh process, the attribute does not exist, so ",[14,562,101],{},[14,564,471],{},[10,566,567,570,571,574,575,577],{},[35,568,569],{},"How do I get a post-mortem prompt automatically when a script crashes?","\nRun ",[14,572,573],{},"python -m pdb -c continue your_script.py",". The ",[14,576,295],{}," runs the script normally, and if it raises an unhandled exception pdb drops into a post-mortem prompt at the failing frame instead of exiting.",[10,579,580,581],{},"← Back to ",[75,582,583],{"href":77},"Interactive Debugging with pdb and ipdb",[585,586,587],"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);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":113,"searchDepth":126,"depth":126,"links":589},[590,591,592,593,594],{"id":26,"depth":126,"text":27},{"id":82,"depth":126,"text":83},{"id":435,"depth":126,"text":436},{"id":458,"depth":126,"text":459},{"id":528,"depth":126,"text":529},"Reopen the exact frame where an exception was raised using pdb.post_mortem and pdb.pm, sys.last_traceback, python -m pdb -c continue, IPython %debug, and pytest --pdb.","md",{"slug":598,"type":599,"breadcrumb":600,"datePublished":601,"dateModified":601,"faq":602,"howto":609},"post-mortem-debugging-with-pdb-pm","long_tail","Post-Mortem pdb.pm()","2026-06-18",[603,605,607],{"q":534,"a":604},"pdb.post_mortem(tb) starts a post-mortem session on a traceback you pass it. pdb.pm() is a convenience wrapper that calls post_mortem on sys.last_traceback, the traceback of the most recent unhandled exception stored by the interpreter, so you can debug a crash that already printed.",{"q":552,"a":606},"The interpreter only sets sys.last_traceback, sys.last_value, and sys.last_type when an unhandled exception reaches the top level in interactive mode. Inside a script that caught the exception, or in a fresh process, the attribute does not exist, so pdb.pm() raises AttributeError.",{"q":569,"a":608},"Run python -m pdb -c continue your_script.py. The -c continue runs the script normally, and if it raises an unhandled exception pdb drops into a post-mortem prompt at the failing frame instead of exiting.",{"name":610,"description":611,"steps":612},"How to debug a crash post-mortem with pdb","Reopen the frame where an exception was raised, without re-running the program, to inspect the state at the moment of failure.",[613,616,619,622],{"name":614,"text":615},"Trigger or capture the exception","Let the unhandled exception propagate so the interpreter stores sys.last_traceback, or catch it and keep the traceback object.",{"name":617,"text":618},"Enter post-mortem","Call pdb.pm() for the last exception, or pdb.post_mortem(tb) for a specific traceback object.",{"name":620,"text":621},"Inspect the failing frame","Use up and down to walk the stack and p to read the locals that existed at the moment of the crash.",{"name":623,"text":624},"Automate it for scripts and tests","Launch with python -m pdb -c continue or run pytest --pdb to drop into post-mortem on any failure.","\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002Fpost-mortem-debugging-with-pdb-pm",{"title":5,"description":595},"systematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002Fpost-mortem-debugging-with-pdb-pm\u002Findex","1MSF-CQyADOX8xij-j3q2Gzhb3YlS-581oO-v7zNoCA",1781793487406]