[{"data":1,"prerenderedAt":849},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-common-gotchas-explained":3},{"id":4,"title":5,"body":6,"description":835,"difficulty":836,"extension":837,"framework":838,"frameworkSlug":35,"meta":839,"navigation":87,"order":75,"path":840,"qaPath":841,"seo":842,"stem":843,"subtopic":844,"topic":845,"topicSlug":846,"updated":847,"__hash__":848},"blog\u002Fblog\u002Fpython-common-gotchas-explained.md","Python Common Gotchas Explained — Mutable Defaults, Late Binding, and Other Traps",{"type":7,"value":8,"toc":825},"minimark",[9,14,18,22,30,118,125,185,189,201,261,264,316,320,323,446,453,457,473,538,559,563,566,662,669,673,688,771,775,821],[10,11,13],"h2",{"id":12},"python-common-gotchas-explained","Python common gotchas, explained",[15,16,17],"p",{},"Python is friendly, but a handful of behaviours surprise almost everyone at least once.\nMost stem from a few core facts — when default arguments are evaluated, how closures capture\nvariables, and the difference between identity and equality. Knowing these turns \"mysterious\nbug\" into \"oh, of course.\"",[10,19,21],{"id":20},"the-mutable-default-argument","The mutable default argument",[15,23,24,25,29],{},"A default argument is evaluated ",[26,27,28],"strong",{},"once",", when the function is defined — not on each call. A\nmutable default (list, dict, set) is therefore shared across every call.",[31,32,37],"pre",{"className":33,"code":34,"language":35,"meta":36,"style":36},"language-python shiki shiki-themes github-light github-dark","def add(item, bucket=[]):       # the list is created ONCE\n    bucket.append(item)\n    return bucket\n\nadd(1)    # [1]\nadd(2)    # [1, 2]  ← surprise! same list reused\n","python","",[38,39,40,67,73,82,89,105],"code",{"__ignoreMap":36},[41,42,45,49,53,57,60,63],"span",{"class":43,"line":44},"line",1,[41,46,48],{"class":47},"szBVR","def",[41,50,52],{"class":51},"sScJk"," add",[41,54,56],{"class":55},"sVt8B","(item, bucket",[41,58,59],{"class":47},"=",[41,61,62],{"class":55},"[]):       ",[41,64,66],{"class":65},"sJ8bj","# the list is created ONCE\n",[41,68,70],{"class":43,"line":69},2,[41,71,72],{"class":55},"    bucket.append(item)\n",[41,74,76,79],{"class":43,"line":75},3,[41,77,78],{"class":47},"    return",[41,80,81],{"class":55}," bucket\n",[41,83,85],{"class":43,"line":84},4,[41,86,88],{"emptyLinePlaceholder":87},true,"\n",[41,90,92,95,99,102],{"class":43,"line":91},5,[41,93,94],{"class":55},"add(",[41,96,98],{"class":97},"sj4cs","1",[41,100,101],{"class":55},")    ",[41,103,104],{"class":65},"# [1]\n",[41,106,108,110,113,115],{"class":43,"line":107},6,[41,109,94],{"class":55},[41,111,112],{"class":97},"2",[41,114,101],{"class":55},[41,116,117],{"class":65},"# [1, 2]  ← surprise! same list reused\n",[15,119,120,121,124],{},"The fix is the standard ",[38,122,123],{},"None"," sentinel:",[31,126,128],{"className":33,"code":127,"language":35,"meta":36,"style":36},"def add(item, bucket=None):\n    if bucket is None:\n        bucket = []             # fresh list each call\n    bucket.append(item)\n    return bucket\n",[38,129,130,145,162,175,179],{"__ignoreMap":36},[41,131,132,134,136,138,140,142],{"class":43,"line":44},[41,133,48],{"class":47},[41,135,52],{"class":51},[41,137,56],{"class":55},[41,139,59],{"class":47},[41,141,123],{"class":97},[41,143,144],{"class":55},"):\n",[41,146,147,150,153,156,159],{"class":43,"line":69},[41,148,149],{"class":47},"    if",[41,151,152],{"class":55}," bucket ",[41,154,155],{"class":47},"is",[41,157,158],{"class":97}," None",[41,160,161],{"class":55},":\n",[41,163,164,167,169,172],{"class":43,"line":75},[41,165,166],{"class":55},"        bucket ",[41,168,59],{"class":47},[41,170,171],{"class":55}," []             ",[41,173,174],{"class":65},"# fresh list each call\n",[41,176,177],{"class":43,"line":84},[41,178,72],{"class":55},[41,180,181,183],{"class":43,"line":91},[41,182,78],{"class":47},[41,184,81],{"class":55},[10,186,188],{"id":187},"late-binding-closures-in-loops","Late-binding closures in loops",[15,190,191,192,195,196,200],{},"Closures capture the ",[26,193,194],{},"variable",", not its value at creation time. Build functions in a\nloop and they all see the loop variable's ",[197,198,199],"em",{},"final"," value.",[31,202,204],{"className":33,"code":203,"language":35,"meta":36,"style":36},"funcs = [lambda: i for i in range(3)]\n[f() for f in funcs]            # [2, 2, 2] — not [0, 1, 2]!\n",[38,205,206,243],{"__ignoreMap":36},[41,207,208,211,213,216,219,222,225,228,231,234,237,240],{"class":43,"line":44},[41,209,210],{"class":55},"funcs ",[41,212,59],{"class":47},[41,214,215],{"class":55}," [",[41,217,218],{"class":47},"lambda",[41,220,221],{"class":55},": i ",[41,223,224],{"class":47},"for",[41,226,227],{"class":55}," i ",[41,229,230],{"class":47},"in",[41,232,233],{"class":97}," range",[41,235,236],{"class":55},"(",[41,238,239],{"class":97},"3",[41,241,242],{"class":55},")]\n",[41,244,245,248,250,253,255,258],{"class":43,"line":69},[41,246,247],{"class":55},"[f() ",[41,249,224],{"class":47},[41,251,252],{"class":55}," f ",[41,254,230],{"class":47},[41,256,257],{"class":55}," funcs]            ",[41,259,260],{"class":65},"# [2, 2, 2] — not [0, 1, 2]!\n",[15,262,263],{},"Capture the value explicitly with a default argument (evaluated at definition time):",[31,265,267],{"className":33,"code":266,"language":35,"meta":36,"style":36},"funcs = [lambda i=i: i for i in range(3)]\n[f() for f in funcs]            # [0, 1, 2]\n",[38,268,269,301],{"__ignoreMap":36},[41,270,271,273,275,277,279,282,284,287,289,291,293,295,297,299],{"class":43,"line":44},[41,272,210],{"class":55},[41,274,59],{"class":47},[41,276,215],{"class":55},[41,278,218],{"class":47},[41,280,281],{"class":55}," i",[41,283,59],{"class":47},[41,285,286],{"class":55},"i: i ",[41,288,224],{"class":47},[41,290,227],{"class":55},[41,292,230],{"class":47},[41,294,233],{"class":97},[41,296,236],{"class":55},[41,298,239],{"class":97},[41,300,242],{"class":55},[41,302,303,305,307,309,311,313],{"class":43,"line":69},[41,304,247],{"class":55},[41,306,224],{"class":47},[41,308,252],{"class":55},[41,310,230],{"class":47},[41,312,257],{"class":55},[41,314,315],{"class":65},"# [0, 1, 2]\n",[10,317,319],{"id":318},"modifying-a-list-while-iterating-it","Modifying a list while iterating it",[15,321,322],{},"Mutating a collection during iteration skips elements or raises, because the iterator's\nindex drifts as the list shrinks.",[31,324,326],{"className":33,"code":325,"language":35,"meta":36,"style":36},"nums = [1, 2, 3, 4]\nfor n in nums:\n    if n % 2 == 0:\n        nums.remove(n)          # skips elements — buggy\n# Result is [1, 3] only by luck; logic is broken\n\n# Iterate a copy, or build a new list:\nnums = [n for n in nums if n % 2 != 0]\n",[38,327,328,356,368,388,396,401,405,411],{"__ignoreMap":36},[41,329,330,333,335,337,339,342,344,346,348,350,353],{"class":43,"line":44},[41,331,332],{"class":55},"nums ",[41,334,59],{"class":47},[41,336,215],{"class":55},[41,338,98],{"class":97},[41,340,341],{"class":55},", ",[41,343,112],{"class":97},[41,345,341],{"class":55},[41,347,239],{"class":97},[41,349,341],{"class":55},[41,351,352],{"class":97},"4",[41,354,355],{"class":55},"]\n",[41,357,358,360,363,365],{"class":43,"line":69},[41,359,224],{"class":47},[41,361,362],{"class":55}," n ",[41,364,230],{"class":47},[41,366,367],{"class":55}," nums:\n",[41,369,370,372,374,377,380,383,386],{"class":43,"line":75},[41,371,149],{"class":47},[41,373,362],{"class":55},[41,375,376],{"class":47},"%",[41,378,379],{"class":97}," 2",[41,381,382],{"class":47}," ==",[41,384,385],{"class":97}," 0",[41,387,161],{"class":55},[41,389,390,393],{"class":43,"line":84},[41,391,392],{"class":55},"        nums.remove(n)          ",[41,394,395],{"class":65},"# skips elements — buggy\n",[41,397,398],{"class":43,"line":91},[41,399,400],{"class":65},"# Result is [1, 3] only by luck; logic is broken\n",[41,402,403],{"class":43,"line":107},[41,404,88],{"emptyLinePlaceholder":87},[41,406,408],{"class":43,"line":407},7,[41,409,410],{"class":65},"# Iterate a copy, or build a new list:\n",[41,412,414,416,418,421,423,425,427,430,433,435,437,439,442,444],{"class":43,"line":413},8,[41,415,332],{"class":55},[41,417,59],{"class":47},[41,419,420],{"class":55}," [n ",[41,422,224],{"class":47},[41,424,362],{"class":55},[41,426,230],{"class":47},[41,428,429],{"class":55}," nums ",[41,431,432],{"class":47},"if",[41,434,362],{"class":55},[41,436,376],{"class":47},[41,438,379],{"class":97},[41,440,441],{"class":47}," !=",[41,443,385],{"class":97},[41,445,355],{"class":55},[15,447,448,449,452],{},"The comprehension (or iterating over ",[38,450,451],{},"nums[:]",") avoids touching the thing you're walking.",[10,454,456],{"id":455},"is-vs-and-integer-caching","is vs == and integer caching",[15,458,459,462,463,466,467,462,469,472],{},[38,460,461],{},"=="," compares ",[26,464,465],{},"values","; ",[38,468,155],{},[26,470,471],{},"identity"," (same object). They diverge in ways\nthat look random because CPython caches small integers (-5..256) and some strings.",[31,474,476],{"className":33,"code":475,"language":35,"meta":36,"style":36},"a = 256; b = 256\na is b           # True  — cached, same object\nx = 257; y = 257\nx is y           # often False — different objects, same value\n",[38,477,478,496,508,526],{"__ignoreMap":36},[41,479,480,483,485,488,491,493],{"class":43,"line":44},[41,481,482],{"class":55},"a ",[41,484,59],{"class":47},[41,486,487],{"class":97}," 256",[41,489,490],{"class":55},"; b ",[41,492,59],{"class":47},[41,494,495],{"class":97}," 256\n",[41,497,498,500,502,505],{"class":43,"line":69},[41,499,482],{"class":55},[41,501,155],{"class":47},[41,503,504],{"class":55}," b           ",[41,506,507],{"class":65},"# True  — cached, same object\n",[41,509,510,513,515,518,521,523],{"class":43,"line":75},[41,511,512],{"class":55},"x ",[41,514,59],{"class":47},[41,516,517],{"class":97}," 257",[41,519,520],{"class":55},"; y ",[41,522,59],{"class":47},[41,524,525],{"class":97}," 257\n",[41,527,528,530,532,535],{"class":43,"line":84},[41,529,512],{"class":55},[41,531,155],{"class":47},[41,533,534],{"class":55}," y           ",[41,536,537],{"class":65},"# often False — different objects, same value\n",[15,539,540,541,543,544,546,547,341,549,341,552,555,556,558],{},"Rule: use ",[38,542,461],{}," for values, and reserve ",[38,545,155],{}," for ",[38,548,123],{},[38,550,551],{},"True",[38,553,554],{},"False",", and genuine\nidentity checks. Relying on ",[38,557,155],{}," for numbers or strings is a bug waiting for a bigger value.",[10,560,562],{"id":561},"shallow-vs-deep-copy","Shallow vs deep copy",[15,564,565],{},"Copying a nested structure shallowly duplicates only the outer container; inner objects are\nshared.",[31,567,569],{"className":33,"code":568,"language":35,"meta":36,"style":36},"import copy\noriginal = [[1, 2], [3, 4]]\nshallow = original[:]            # or list(original) \u002F copy.copy\nshallow[0].append(99)\noriginal                        # [[1, 2, 99], [3, 4]] — inner list was shared!\n\ndeep = copy.deepcopy(original)  # fully independent\n",[38,570,571,579,607,620,637,645,649],{"__ignoreMap":36},[41,572,573,576],{"class":43,"line":44},[41,574,575],{"class":47},"import",[41,577,578],{"class":55}," copy\n",[41,580,581,584,586,589,591,593,595,598,600,602,604],{"class":43,"line":69},[41,582,583],{"class":55},"original ",[41,585,59],{"class":47},[41,587,588],{"class":55}," [[",[41,590,98],{"class":97},[41,592,341],{"class":55},[41,594,112],{"class":97},[41,596,597],{"class":55},"], [",[41,599,239],{"class":97},[41,601,341],{"class":55},[41,603,352],{"class":97},[41,605,606],{"class":55},"]]\n",[41,608,609,612,614,617],{"class":43,"line":75},[41,610,611],{"class":55},"shallow ",[41,613,59],{"class":47},[41,615,616],{"class":55}," original[:]            ",[41,618,619],{"class":65},"# or list(original) \u002F copy.copy\n",[41,621,622,625,628,631,634],{"class":43,"line":84},[41,623,624],{"class":55},"shallow[",[41,626,627],{"class":97},"0",[41,629,630],{"class":55},"].append(",[41,632,633],{"class":97},"99",[41,635,636],{"class":55},")\n",[41,638,639,642],{"class":43,"line":91},[41,640,641],{"class":55},"original                        ",[41,643,644],{"class":65},"# [[1, 2, 99], [3, 4]] — inner list was shared!\n",[41,646,647],{"class":43,"line":107},[41,648,88],{"emptyLinePlaceholder":87},[41,650,651,654,656,659],{"class":43,"line":407},[41,652,653],{"class":55},"deep ",[41,655,59],{"class":47},[41,657,658],{"class":55}," copy.deepcopy(original)  ",[41,660,661],{"class":65},"# fully independent\n",[15,663,664,665,668],{},"Reach for ",[38,666,667],{},"copy.deepcopy"," when you need the nested objects to be independent too.",[10,670,672],{"id":671},"floating-point-and-chained-surprises","Floating-point and chained surprises",[15,674,675,676,679,680,683,684,687],{},"Floats are binary approximations, so exact equality often fails — compare with tolerance.\nAnd ",[38,677,678],{},"+="," on a tuple member, or ",[38,681,682],{},"and","\u002F",[38,685,686],{},"or"," returning operands rather than booleans, surprise\nthe unwary.",[31,689,691],{"className":33,"code":690,"language":35,"meta":36,"style":36},"0.1 + 0.2 == 0.3                # False\nimport math\nmath.isclose(0.1 + 0.2, 0.3)    # True\n\n\"a\" or \"b\"                      # 'a' — or returns the first truthy operand, not True\n0 or \"fallback\"                 # 'fallback'\n",[38,692,693,712,719,740,744,759],{"__ignoreMap":36},[41,694,695,698,701,704,706,709],{"class":43,"line":44},[41,696,697],{"class":97},"0.1",[41,699,700],{"class":47}," +",[41,702,703],{"class":97}," 0.2",[41,705,382],{"class":47},[41,707,708],{"class":97}," 0.3",[41,710,711],{"class":65},"                # False\n",[41,713,714,716],{"class":43,"line":69},[41,715,575],{"class":47},[41,717,718],{"class":55}," math\n",[41,720,721,724,726,728,730,732,735,737],{"class":43,"line":75},[41,722,723],{"class":55},"math.isclose(",[41,725,697],{"class":97},[41,727,700],{"class":47},[41,729,703],{"class":97},[41,731,341],{"class":55},[41,733,734],{"class":97},"0.3",[41,736,101],{"class":55},[41,738,739],{"class":65},"# True\n",[41,741,742],{"class":43,"line":84},[41,743,88],{"emptyLinePlaceholder":87},[41,745,746,750,753,756],{"class":43,"line":91},[41,747,749],{"class":748},"sZZnC","\"a\"",[41,751,752],{"class":47}," or",[41,754,755],{"class":748}," \"b\"",[41,757,758],{"class":65},"                      # 'a' — or returns the first truthy operand, not True\n",[41,760,761,763,765,768],{"class":43,"line":107},[41,762,627],{"class":97},[41,764,752],{"class":47},[41,766,767],{"class":748}," \"fallback\"",[41,769,770],{"class":65},"                 # 'fallback'\n",[10,772,774],{"id":773},"recap","Recap",[15,776,777,778,781,782,784,785,788,789,792,793,796,797,799,800,802,803,805,806,808,809,812,813,816,817,820],{},"The classic Python traps share a few roots. ",[26,779,780],{},"Mutable defaults"," are created once — use a\n",[38,783,123],{}," sentinel. ",[26,786,787],{},"Closures"," bind late — capture loop values with ",[38,790,791],{},"i=i",". ",[26,794,795],{},"Don't mutate a\ncollection while iterating"," it; build a new one. Use ",[38,798,461],{}," for values and ",[38,801,155],{}," only for\n",[38,804,123],{},"\u002Fidentity, since small-int and string caching makes ",[38,807,155],{}," deceptive. Remember ",[26,810,811],{},"shallow\ncopies share inner objects"," (use ",[38,814,815],{},"deepcopy","), and that floats need ",[38,818,819],{},"math.isclose",". Each one\nis obvious once you know the underlying rule.",[822,823,824],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":36,"searchDepth":69,"depth":69,"links":826},[827,828,829,830,831,832,833,834],{"id":12,"depth":69,"text":13},{"id":20,"depth":69,"text":21},{"id":187,"depth":69,"text":188},{"id":318,"depth":69,"text":319},{"id":455,"depth":69,"text":456},{"id":561,"depth":69,"text":562},{"id":671,"depth":69,"text":672},{"id":773,"depth":69,"text":774},"The Python gotchas that bite everyone — mutable default arguments, late-binding closures in loops, modifying a list while iterating, is vs ==, and the classic copy and integer-caching surprises.","medium","md","Python",{},"\u002Fblog\u002Fpython-common-gotchas-explained","\u002Fpython\u002Fidioms\u002Fgotchas",{"title":5,"description":835},"blog\u002Fpython-common-gotchas-explained","Common Gotchas & Anti-patterns","Pythonic Idioms","idioms","2026-06-19","pajqASWk48msK4iLG2BYnu2x8moimzruKjQ79932d1Y",1782244093398]