[{"data":1,"prerenderedAt":586},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-eafp-vs-lbyl-explained":3},{"id":4,"title":5,"body":6,"description":572,"difficulty":573,"extension":574,"framework":575,"frameworkSlug":35,"meta":576,"navigation":246,"order":44,"path":577,"qaPath":578,"seo":579,"stem":580,"subtopic":581,"topic":582,"topicSlug":583,"updated":584,"__hash__":585},"blog\u002Fblog\u002Fpython-eafp-vs-lbyl-explained.md","Python EAFP vs LBYL Explained — Why Pythonistas Ask Forgiveness, Not Permission",{"type":7,"value":8,"toc":562},"minimark",[9,14,23,27,30,126,133,137,140,187,190,194,214,284,288,291,380,383,387,394,445,456,460,478,529,533,558],[10,11,13],"h2",{"id":12},"python-eafp-vs-lbyl-explained","Python EAFP vs LBYL, explained",[15,16,17,18,22],"p",{},"EAFP — \"Easier to Ask Forgiveness than Permission\" — and LBYL — \"Look Before You Leap\" — are\nthe two strategies for dealing with operations that might fail. Python culture leans heavily\ntoward EAFP, and knowing ",[19,20,21],"em",{},"why"," reveals something deep about how the language is meant to be\nused.",[10,24,26],{"id":25},"lbyl-check-then-act","LBYL: check, then act",[15,28,29],{},"LBYL guards an operation with explicit tests beforehand. It reads naturally but has subtle\nproblems in Python.",[31,32,37],"pre",{"className":33,"code":34,"language":35,"meta":36,"style":36},"language-python shiki shiki-themes github-light github-dark","# LBYL\nif \"name\" in user and user[\"name\"] is not None:\n    name = user[\"name\"]\nelse:\n    name = \"anonymous\"\n","python","",[38,39,40,49,92,108,116],"code",{"__ignoreMap":36},[41,42,45],"span",{"class":43,"line":44},"line",1,[41,46,48],{"class":47},"sJ8bj","# LBYL\n",[41,50,52,56,60,63,67,70,73,76,79,82,85,89],{"class":43,"line":51},2,[41,53,55],{"class":54},"szBVR","if",[41,57,59],{"class":58},"sZZnC"," \"name\"",[41,61,62],{"class":54}," in",[41,64,66],{"class":65},"sVt8B"," user ",[41,68,69],{"class":54},"and",[41,71,72],{"class":65}," user[",[41,74,75],{"class":58},"\"name\"",[41,77,78],{"class":65},"] ",[41,80,81],{"class":54},"is",[41,83,84],{"class":54}," not",[41,86,88],{"class":87},"sj4cs"," None",[41,90,91],{"class":65},":\n",[41,93,95,98,101,103,105],{"class":43,"line":94},3,[41,96,97],{"class":65},"    name ",[41,99,100],{"class":54},"=",[41,102,72],{"class":65},[41,104,75],{"class":58},[41,106,107],{"class":65},"]\n",[41,109,111,114],{"class":43,"line":110},4,[41,112,113],{"class":54},"else",[41,115,91],{"class":65},[41,117,119,121,123],{"class":43,"line":118},5,[41,120,97],{"class":65},[41,122,100],{"class":54},[41,124,125],{"class":58}," \"anonymous\"\n",[15,127,128,129,132],{},"You check the precondition, then perform the action. The trouble is the checks can become a\nthicket, and — more seriously — the world can change ",[19,130,131],{},"between"," the check and the action.",[10,134,136],{"id":135},"eafp-just-try-it","EAFP: just try it",[15,138,139],{},"EAFP assumes the operation will succeed and handles the exception if it doesn't. It's the\nPythonic default.",[31,141,143],{"className":33,"code":142,"language":35,"meta":36,"style":36},"# EAFP\ntry:\n    name = user[\"name\"]\nexcept KeyError:\n    name = \"anonymous\"\n",[38,144,145,150,157,169,179],{"__ignoreMap":36},[41,146,147],{"class":43,"line":44},[41,148,149],{"class":47},"# EAFP\n",[41,151,152,155],{"class":43,"line":51},[41,153,154],{"class":54},"try",[41,156,91],{"class":65},[41,158,159,161,163,165,167],{"class":43,"line":94},[41,160,97],{"class":65},[41,162,100],{"class":54},[41,164,72],{"class":65},[41,166,75],{"class":58},[41,168,107],{"class":65},[41,170,171,174,177],{"class":43,"line":110},[41,172,173],{"class":54},"except",[41,175,176],{"class":87}," KeyError",[41,178,91],{"class":65},[41,180,181,183,185],{"class":43,"line":118},[41,182,97],{"class":65},[41,184,100],{"class":54},[41,186,125],{"class":58},[15,188,189],{},"This expresses intent directly: \"get the name; if there isn't one, fall back.\" There's no\nduplicated key lookup and no precondition logic to keep in sync with the action.",[10,191,193],{"id":192},"why-eafp-is-usually-preferred","Why EAFP is usually preferred",[15,195,196,197,201,202,205,206,209,210,213],{},"Three reasons. First, ",[198,199,200],"strong",{},"no time-of-check\u002Ftime-of-use race",": with LBYL, a file can be\ndeleted between ",[38,203,204],{},"os.path.exists"," and ",[38,207,208],{},"open",". Second, it's often ",[198,211,212],{},"faster on the happy path","\n— exceptions are cheap when they don't fire, and you skip a redundant check. Third, it\nhandles cases the check might miss (a value that's present but wrong type).",[31,215,217],{"className":33,"code":216,"language":35,"meta":36,"style":36},"# LBYL has a race condition:\nif os.path.exists(path):\n    open(path)                # file may be gone by now → crash\n\n# EAFP has none:\ntry:\n    open(path)\nexcept FileNotFoundError:\n    handle_missing()\n",[38,218,219,224,231,242,248,253,260,268,278],{"__ignoreMap":36},[41,220,221],{"class":43,"line":44},[41,222,223],{"class":47},"# LBYL has a race condition:\n",[41,225,226,228],{"class":43,"line":51},[41,227,55],{"class":54},[41,229,230],{"class":65}," os.path.exists(path):\n",[41,232,233,236,239],{"class":43,"line":94},[41,234,235],{"class":87},"    open",[41,237,238],{"class":65},"(path)                ",[41,240,241],{"class":47},"# file may be gone by now → crash\n",[41,243,244],{"class":43,"line":110},[41,245,247],{"emptyLinePlaceholder":246},true,"\n",[41,249,250],{"class":43,"line":118},[41,251,252],{"class":47},"# EAFP has none:\n",[41,254,256,258],{"class":43,"line":255},6,[41,257,154],{"class":54},[41,259,91],{"class":65},[41,261,263,265],{"class":43,"line":262},7,[41,264,235],{"class":87},[41,266,267],{"class":65},"(path)\n",[41,269,271,273,276],{"class":43,"line":270},8,[41,272,173],{"class":54},[41,274,275],{"class":87}," FileNotFoundError",[41,277,91],{"class":65},[41,279,281],{"class":43,"line":280},9,[41,282,283],{"class":65},"    handle_missing()\n",[10,285,287],{"id":286},"duck-typing-and-eafp-go-together","Duck typing and EAFP go together",[15,289,290],{},"Python's duck typing — \"if it quacks like a duck\" — is EAFP applied to types. Rather than\nchecking an object's type, try the operation and let it fail if unsupported.",[31,292,294],{"className":33,"code":293,"language":35,"meta":36,"style":36},"# Un-Pythonic LBYL on types:\nif isinstance(x, (list, tuple)):\n    process(x)\n\n# EAFP \u002F duck typing:\ntry:\n    iter(x)             # works for any iterable, not just list\u002Ftuple\nexcept TypeError:\n    raise ValueError(\"need an iterable\")\n",[38,295,296,301,323,328,332,337,343,354,363],{"__ignoreMap":36},[41,297,298],{"class":43,"line":44},[41,299,300],{"class":47},"# Un-Pythonic LBYL on types:\n",[41,302,303,305,308,311,314,317,320],{"class":43,"line":51},[41,304,55],{"class":54},[41,306,307],{"class":87}," isinstance",[41,309,310],{"class":65},"(x, (",[41,312,313],{"class":87},"list",[41,315,316],{"class":65},", ",[41,318,319],{"class":87},"tuple",[41,321,322],{"class":65},")):\n",[41,324,325],{"class":43,"line":94},[41,326,327],{"class":65},"    process(x)\n",[41,329,330],{"class":43,"line":110},[41,331,247],{"emptyLinePlaceholder":246},[41,333,334],{"class":43,"line":118},[41,335,336],{"class":47},"# EAFP \u002F duck typing:\n",[41,338,339,341],{"class":43,"line":255},[41,340,154],{"class":54},[41,342,91],{"class":65},[41,344,345,348,351],{"class":43,"line":262},[41,346,347],{"class":87},"    iter",[41,349,350],{"class":65},"(x)             ",[41,352,353],{"class":47},"# works for any iterable, not just list\u002Ftuple\n",[41,355,356,358,361],{"class":43,"line":270},[41,357,173],{"class":54},[41,359,360],{"class":87}," TypeError",[41,362,91],{"class":65},[41,364,365,368,371,374,377],{"class":43,"line":280},[41,366,367],{"class":54},"    raise",[41,369,370],{"class":87}," ValueError",[41,372,373],{"class":65},"(",[41,375,376],{"class":58},"\"need an iterable\"",[41,378,379],{"class":65},")\n",[15,381,382],{},"This makes code work with any type that supports the behaviour, not just the ones you\nanticipated.",[10,384,386],{"id":385},"when-lbyl-is-the-better-choice","When LBYL is the better choice",[15,388,389,390,393],{},"EAFP isn't dogma. Prefer LBYL when the ",[198,391,392],{},"check is cheap and the failure is expensive or\ncommon"," — for example validating user input before a costly operation, or when an exception\nmid-operation would leave state half-changed.",[31,395,397],{"className":33,"code":396,"language":35,"meta":36,"style":36},"# LBYL is clearer here — validate before doing irreversible work\nif not 0 \u003C= percent \u003C= 100:\n    raise ValueError(\"percent out of range\")\napply_discount(percent)\n",[38,398,399,404,427,440],{"__ignoreMap":36},[41,400,401],{"class":43,"line":44},[41,402,403],{"class":47},"# LBYL is clearer here — validate before doing irreversible work\n",[41,405,406,408,410,413,416,419,422,425],{"class":43,"line":51},[41,407,55],{"class":54},[41,409,84],{"class":54},[41,411,412],{"class":87}," 0",[41,414,415],{"class":54}," \u003C=",[41,417,418],{"class":65}," percent ",[41,420,421],{"class":54},"\u003C=",[41,423,424],{"class":87}," 100",[41,426,91],{"class":65},[41,428,429,431,433,435,438],{"class":43,"line":94},[41,430,367],{"class":54},[41,432,370],{"class":87},[41,434,373],{"class":65},[41,436,437],{"class":58},"\"percent out of range\"",[41,439,379],{"class":65},[41,441,442],{"class":43,"line":110},[41,443,444],{"class":65},"apply_discount(percent)\n",[15,446,447,448,451,452,455],{},"Also use LBYL when an exception would be ",[19,449,450],{},"expected control flow"," hundreds of times per\nsecond — a simple ",[38,453,454],{},"in"," check avoids the exception overhead in a hot loop.",[10,457,459],{"id":458},"use-the-right-built-in-helper","Use the right built-in helper",[15,461,462,463,466,467,470,471,474,475,477],{},"Often neither raw style is needed — Python provides idioms that bake in EAFP. ",[38,464,465],{},"dict.get","\nwith a default, ",[38,468,469],{},"getattr"," with a fallback, and ",[38,472,473],{},"collections.defaultdict"," express \"try, else\ndefault\" without a ",[38,476,154],{}," block at all.",[31,479,481],{"className":33,"code":480,"language":35,"meta":36,"style":36},"name = user.get(\"name\", \"anonymous\")     # cleaner than try\u002Fexcept KeyError\nval = getattr(obj, \"attr\", None)\n",[38,482,483,506],{"__ignoreMap":36},[41,484,485,488,490,493,495,497,500,503],{"class":43,"line":44},[41,486,487],{"class":65},"name ",[41,489,100],{"class":54},[41,491,492],{"class":65}," user.get(",[41,494,75],{"class":58},[41,496,316],{"class":65},[41,498,499],{"class":58},"\"anonymous\"",[41,501,502],{"class":65},")     ",[41,504,505],{"class":47},"# cleaner than try\u002Fexcept KeyError\n",[41,507,508,511,513,516,519,522,524,527],{"class":43,"line":51},[41,509,510],{"class":65},"val ",[41,512,100],{"class":54},[41,514,515],{"class":87}," getattr",[41,517,518],{"class":65},"(obj, ",[41,520,521],{"class":58},"\"attr\"",[41,523,316],{"class":65},[41,525,526],{"class":87},"None",[41,528,379],{"class":65},[10,530,532],{"id":531},"recap","Recap",[15,534,535,538,539,542,543,546,547,316,549,551,552,555,556,477],{},[198,536,537],{},"EAFP"," (try the operation, catch the exception) is the Pythonic default: it avoids\ntime-of-check\u002Ftime-of-use races, is fast on the happy path, and pairs naturally with ",[198,540,541],{},"duck\ntyping"," — try the behaviour instead of checking the type. ",[198,544,545],{},"LBYL"," (check first) is better\nwhen the check is cheap and failure is expensive, common, or would corrupt state mid-way.\nAnd often the cleanest answer is a built-in like ",[38,548,465],{},[38,550,469],{},", or ",[38,553,554],{},"defaultdict","\nthat expresses \"try, else default\" with no ",[38,557,154],{},[559,560,561],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .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":36,"searchDepth":51,"depth":51,"links":563},[564,565,566,567,568,569,570,571],{"id":12,"depth":51,"text":13},{"id":25,"depth":51,"text":26},{"id":135,"depth":51,"text":136},{"id":192,"depth":51,"text":193},{"id":286,"depth":51,"text":287},{"id":385,"depth":51,"text":386},{"id":458,"depth":51,"text":459},{"id":531,"depth":51,"text":532},"The two coding styles for handling possible failure in Python — EAFP (try\u002Fexcept) vs LBYL (check first) — why EAFP is usually more Pythonic, and when LBYL and race conditions tip the balance.","medium","md","Python",{},"\u002Fblog\u002Fpython-eafp-vs-lbyl-explained","\u002Fpython\u002Fidioms\u002Feafp-lbyl",{"title":5,"description":572},"blog\u002Fpython-eafp-vs-lbyl-explained","EAFP vs LBYL","Pythonic Idioms","idioms","2026-06-19","xb1vqX4gG3KXRoU7arnNvisAbl1CCwLPBy5ygUuo1JE",1782244092054]