[{"data":1,"prerenderedAt":3321},["ShallowReactive",2],{"page-\u002Fpython\u002F11-closures-and-decorators":3},{"id":4,"title":5,"body":6,"description":32,"extension":3315,"meta":3316,"navigation":61,"path":3317,"seo":3318,"stem":3319,"__hash__":3320},"content\u002Fpython\u002F11-closures-and-decorators.md","11 — Closures & Decorators",{"type":7,"value":8,"toc":3295},"minimark",[9,13,23,567,574,586,666,792,804,808,875,895,1037,1040,1044,1054,1243,1248,1468,1483,1487,1494,1812,1848,1852,2149,2153,2324,2346,2350,2357,2551,2586,2590,2593,2800,2807,2811,2887,2891,2981,2985,2988,3106,3238,3242,3291],[10,11,5],"h1",{"id":12},"_11-closures-decorators",[14,15,17,18,22],"h2",{"id":16},"closure-mechanics-__closure__-and-cell-objects","Closure Mechanics — ",[19,20,21],"code",{},"__closure__"," and Cell Objects",[24,25,27],"code-wrapper",{"language":26},"python",[28,29,33],"pre",{"className":30,"code":31,"language":26,"meta":32,"style":32},"language-python shiki shiki-themes github-light github-dark","# ── Under the hood: closures store variables in \"cell\" objects, not a dict ──\n# A cell is a mutable container shared between the enclosing scope and the closure.\n# The closure doesn't copy the value — it holds a REFERENCE to the same cell.\n\ndef make_counter(start: int = 0):\n    \"\"\"Each call creates a fresh scope with its own cell for `count`.\"\"\"\n    count = start                    # local variable in make_counter's scope\n\n    def increment() -> int:\n        nonlocal count               # binds `count` to the ENCLOSING cell, not a new local\n        count += 1\n        return count\n\n    return increment\n\ncounter_a = make_counter(10)\ncounter_b = make_counter(100)\n\nprint(counter_a())   # 11 — counter_a's cell holds count=10, incremented to 11\nprint(counter_a())   # 12 — same cell, persistent state\nprint(counter_b())   # 101 — DIFFERENT cell, independent state\n\n# Introspect the closure's captured cells — the actual mechanism\nprint(counter_a.__closure__)                         # (\u003Ccell at 0x...: int object at 0x...>,)\nprint(counter_a.__closure__[0].cell_contents)        # 12 — the current value in the cell\n\n# ── The late-binding trap: closures capture VARIABLES, not VALUES ──\n# ANTI-PATTERN: creating closures in a loop — they all share the SAME loop variable\ncallbacks = []\nfor i in range(3):\n    callbacks.append(lambda: i)      # captures `i` by reference, not by value at creation\n\nprint([cb() for cb in callbacks])    # [2, 2, 2] — all return the FINAL value of i\n\n# FIX 1: default argument — evaluated once at def time, captures the CURRENT value\ncallbacks_fixed = []\nfor i in range(3):\n    callbacks_fixed.append(lambda i=i: i)   # i=i binds current i as default arg\n\nprint([cb() for cb in callbacks_fixed])    # [0, 1, 2] — correct\n\n# FIX 2: factory function — each call creates a genuinely new scope with its own cell\ndef make_callback(i: int):\n    return lambda: i               # `i` here is a parameter — fresh cell per call\n\ncallbacks_factory = [make_callback(i) for i in range(3)]\nprint([cb() for cb in callbacks_factory])  # [0, 1, 2] — correct\n","",[19,34,35,44,50,56,63,91,98,113,118,135,147,159,168,173,182,187,204,219,224,236,246,257,262,268,284,305,310,316,322,333,356,371,376,397,402,408,418,435,454,459,478,483,489,504,518,523,549],{"__ignoreMap":32},[36,37,40],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"sdCPZ","# ── Under the hood: closures store variables in \"cell\" objects, not a dict ──\n",[36,45,47],{"class":38,"line":46},2,[36,48,49],{"class":42},"# A cell is a mutable container shared between the enclosing scope and the closure.\n",[36,51,53],{"class":38,"line":52},3,[36,54,55],{"class":42},"# The closure doesn't copy the value — it holds a REFERENCE to the same cell.\n",[36,57,59],{"class":38,"line":58},4,[36,60,62],{"emptyLinePlaceholder":61},true,"\n",[36,64,66,70,74,78,82,85,88],{"class":38,"line":65},5,[36,67,69],{"class":68},"svdQ7","def",[36,71,73],{"class":72},"sIsaT"," make_counter",[36,75,77],{"class":76},"ssxIu","(start: ",[36,79,81],{"class":80},"snvgF","int",[36,83,84],{"class":68}," =",[36,86,87],{"class":80}," 0",[36,89,90],{"class":76},"):\n",[36,92,94],{"class":38,"line":93},6,[36,95,97],{"class":96},"sJ6F3","    \"\"\"Each call creates a fresh scope with its own cell for `count`.\"\"\"\n",[36,99,101,104,107,110],{"class":38,"line":100},7,[36,102,103],{"class":76},"    count ",[36,105,106],{"class":68},"=",[36,108,109],{"class":76}," start                    ",[36,111,112],{"class":42},"# local variable in make_counter's scope\n",[36,114,116],{"class":38,"line":115},8,[36,117,62],{"emptyLinePlaceholder":61},[36,119,121,124,127,130,132],{"class":38,"line":120},9,[36,122,123],{"class":68},"    def",[36,125,126],{"class":72}," increment",[36,128,129],{"class":76},"() -> ",[36,131,81],{"class":80},[36,133,134],{"class":76},":\n",[36,136,138,141,144],{"class":38,"line":137},10,[36,139,140],{"class":68},"        nonlocal",[36,142,143],{"class":76}," count               ",[36,145,146],{"class":42},"# binds `count` to the ENCLOSING cell, not a new local\n",[36,148,150,153,156],{"class":38,"line":149},11,[36,151,152],{"class":76},"        count ",[36,154,155],{"class":68},"+=",[36,157,158],{"class":80}," 1\n",[36,160,162,165],{"class":38,"line":161},12,[36,163,164],{"class":68},"        return",[36,166,167],{"class":76}," count\n",[36,169,171],{"class":38,"line":170},13,[36,172,62],{"emptyLinePlaceholder":61},[36,174,176,179],{"class":38,"line":175},14,[36,177,178],{"class":68},"    return",[36,180,181],{"class":76}," increment\n",[36,183,185],{"class":38,"line":184},15,[36,186,62],{"emptyLinePlaceholder":61},[36,188,190,193,195,198,201],{"class":38,"line":189},16,[36,191,192],{"class":76},"counter_a ",[36,194,106],{"class":68},[36,196,197],{"class":76}," make_counter(",[36,199,200],{"class":80},"10",[36,202,203],{"class":76},")\n",[36,205,207,210,212,214,217],{"class":38,"line":206},17,[36,208,209],{"class":76},"counter_b ",[36,211,106],{"class":68},[36,213,197],{"class":76},[36,215,216],{"class":80},"100",[36,218,203],{"class":76},[36,220,222],{"class":38,"line":221},18,[36,223,62],{"emptyLinePlaceholder":61},[36,225,227,230,233],{"class":38,"line":226},19,[36,228,229],{"class":80},"print",[36,231,232],{"class":76},"(counter_a())   ",[36,234,235],{"class":42},"# 11 — counter_a's cell holds count=10, incremented to 11\n",[36,237,239,241,243],{"class":38,"line":238},20,[36,240,229],{"class":80},[36,242,232],{"class":76},[36,244,245],{"class":42},"# 12 — same cell, persistent state\n",[36,247,249,251,254],{"class":38,"line":248},21,[36,250,229],{"class":80},[36,252,253],{"class":76},"(counter_b())   ",[36,255,256],{"class":42},"# 101 — DIFFERENT cell, independent state\n",[36,258,260],{"class":38,"line":259},22,[36,261,62],{"emptyLinePlaceholder":61},[36,263,265],{"class":38,"line":264},23,[36,266,267],{"class":42},"# Introspect the closure's captured cells — the actual mechanism\n",[36,269,271,273,276,278,281],{"class":38,"line":270},24,[36,272,229],{"class":80},[36,274,275],{"class":76},"(counter_a.",[36,277,21],{"class":80},[36,279,280],{"class":76},")                         ",[36,282,283],{"class":42},"# (\u003Ccell at 0x...: int object at 0x...>,)\n",[36,285,287,289,291,293,296,299,302],{"class":38,"line":286},25,[36,288,229],{"class":80},[36,290,275],{"class":76},[36,292,21],{"class":80},[36,294,295],{"class":76},"[",[36,297,298],{"class":80},"0",[36,300,301],{"class":76},"].cell_contents)        ",[36,303,304],{"class":42},"# 12 — the current value in the cell\n",[36,306,308],{"class":38,"line":307},26,[36,309,62],{"emptyLinePlaceholder":61},[36,311,313],{"class":38,"line":312},27,[36,314,315],{"class":42},"# ── The late-binding trap: closures capture VARIABLES, not VALUES ──\n",[36,317,319],{"class":38,"line":318},28,[36,320,321],{"class":42},"# ANTI-PATTERN: creating closures in a loop — they all share the SAME loop variable\n",[36,323,325,328,330],{"class":38,"line":324},29,[36,326,327],{"class":76},"callbacks ",[36,329,106],{"class":68},[36,331,332],{"class":76}," []\n",[36,334,336,339,342,345,348,351,354],{"class":38,"line":335},30,[36,337,338],{"class":68},"for",[36,340,341],{"class":76}," i ",[36,343,344],{"class":68},"in",[36,346,347],{"class":80}," range",[36,349,350],{"class":76},"(",[36,352,353],{"class":80},"3",[36,355,90],{"class":76},[36,357,359,362,365,368],{"class":38,"line":358},31,[36,360,361],{"class":76},"    callbacks.append(",[36,363,364],{"class":68},"lambda",[36,366,367],{"class":76},": i)      ",[36,369,370],{"class":42},"# captures `i` by reference, not by value at creation\n",[36,372,374],{"class":38,"line":373},32,[36,375,62],{"emptyLinePlaceholder":61},[36,377,379,381,384,386,389,391,394],{"class":38,"line":378},33,[36,380,229],{"class":80},[36,382,383],{"class":76},"([cb() ",[36,385,338],{"class":68},[36,387,388],{"class":76}," cb ",[36,390,344],{"class":68},[36,392,393],{"class":76}," callbacks])    ",[36,395,396],{"class":42},"# [2, 2, 2] — all return the FINAL value of i\n",[36,398,400],{"class":38,"line":399},34,[36,401,62],{"emptyLinePlaceholder":61},[36,403,405],{"class":38,"line":404},35,[36,406,407],{"class":42},"# FIX 1: default argument — evaluated once at def time, captures the CURRENT value\n",[36,409,411,414,416],{"class":38,"line":410},36,[36,412,413],{"class":76},"callbacks_fixed ",[36,415,106],{"class":68},[36,417,332],{"class":76},[36,419,421,423,425,427,429,431,433],{"class":38,"line":420},37,[36,422,338],{"class":68},[36,424,341],{"class":76},[36,426,344],{"class":68},[36,428,347],{"class":80},[36,430,350],{"class":76},[36,432,353],{"class":80},[36,434,90],{"class":76},[36,436,438,441,443,446,448,451],{"class":38,"line":437},38,[36,439,440],{"class":76},"    callbacks_fixed.append(",[36,442,364],{"class":68},[36,444,445],{"class":76}," i",[36,447,106],{"class":68},[36,449,450],{"class":76},"i: i)   ",[36,452,453],{"class":42},"# i=i binds current i as default arg\n",[36,455,457],{"class":38,"line":456},39,[36,458,62],{"emptyLinePlaceholder":61},[36,460,462,464,466,468,470,472,475],{"class":38,"line":461},40,[36,463,229],{"class":80},[36,465,383],{"class":76},[36,467,338],{"class":68},[36,469,388],{"class":76},[36,471,344],{"class":68},[36,473,474],{"class":76}," callbacks_fixed])    ",[36,476,477],{"class":42},"# [0, 1, 2] — correct\n",[36,479,481],{"class":38,"line":480},41,[36,482,62],{"emptyLinePlaceholder":61},[36,484,486],{"class":38,"line":485},42,[36,487,488],{"class":42},"# FIX 2: factory function — each call creates a genuinely new scope with its own cell\n",[36,490,492,494,497,500,502],{"class":38,"line":491},43,[36,493,69],{"class":68},[36,495,496],{"class":72}," make_callback",[36,498,499],{"class":76},"(i: ",[36,501,81],{"class":80},[36,503,90],{"class":76},[36,505,507,509,512,515],{"class":38,"line":506},44,[36,508,178],{"class":68},[36,510,511],{"class":68}," lambda",[36,513,514],{"class":76},": i               ",[36,516,517],{"class":42},"# `i` here is a parameter — fresh cell per call\n",[36,519,521],{"class":38,"line":520},45,[36,522,62],{"emptyLinePlaceholder":61},[36,524,526,529,531,534,536,538,540,542,544,546],{"class":38,"line":525},46,[36,527,528],{"class":76},"callbacks_factory ",[36,530,106],{"class":68},[36,532,533],{"class":76}," [make_callback(i) ",[36,535,338],{"class":68},[36,537,341],{"class":76},[36,539,344],{"class":68},[36,541,347],{"class":80},[36,543,350],{"class":76},[36,545,353],{"class":80},[36,547,548],{"class":76},")]\n",[36,550,552,554,556,558,560,562,565],{"class":38,"line":551},47,[36,553,229],{"class":80},[36,555,383],{"class":76},[36,557,338],{"class":68},[36,559,388],{"class":76},[36,561,344],{"class":68},[36,563,564],{"class":76}," callbacks_factory])  ",[36,566,477],{"class":42},[14,568,570,573],{"id":569},"nonlocal-mutating-an-enclosing-variable",[19,571,572],{},"nonlocal"," — Mutating an Enclosing Variable",[575,576,577,578,582,583,585],"p",{},"By default, assigning to a name inside a nested function creates a ",[579,580,581],"strong",{},"new local variable"," in that nested function, shadowing the outer one rather than modifying it. ",[19,584,572],{}," opts out of that.",[24,587,588],{"language":26},[28,589,591],{"className":30,"code":590,"language":26,"meta":32,"style":32},"def make_counter():\n    count = 0\n    def increment():\n        count += 1     # UnboundLocalError without nonlocal — this is an assignment,\n        return count    # which makes `count` local to `increment` by default\n    return increment\n\ncounter = make_counter()\n# counter()   # UnboundLocalError: local variable 'count' referenced before assignment\n",[19,592,593,602,611,619,631,641,647,651,661],{"__ignoreMap":32},[36,594,595,597,599],{"class":38,"line":39},[36,596,69],{"class":68},[36,598,73],{"class":72},[36,600,601],{"class":76},"():\n",[36,603,604,606,608],{"class":38,"line":46},[36,605,103],{"class":76},[36,607,106],{"class":68},[36,609,610],{"class":80}," 0\n",[36,612,613,615,617],{"class":38,"line":52},[36,614,123],{"class":68},[36,616,126],{"class":72},[36,618,601],{"class":76},[36,620,621,623,625,628],{"class":38,"line":58},[36,622,152],{"class":76},[36,624,155],{"class":68},[36,626,627],{"class":80}," 1",[36,629,630],{"class":42},"     # UnboundLocalError without nonlocal — this is an assignment,\n",[36,632,633,635,638],{"class":38,"line":65},[36,634,164],{"class":68},[36,636,637],{"class":76}," count    ",[36,639,640],{"class":42},"# which makes `count` local to `increment` by default\n",[36,642,643,645],{"class":38,"line":93},[36,644,178],{"class":68},[36,646,181],{"class":76},[36,648,649],{"class":38,"line":100},[36,650,62],{"emptyLinePlaceholder":61},[36,652,653,656,658],{"class":38,"line":115},[36,654,655],{"class":76},"counter ",[36,657,106],{"class":68},[36,659,660],{"class":76}," make_counter()\n",[36,662,663],{"class":38,"line":120},[36,664,665],{"class":42},"# counter()   # UnboundLocalError: local variable 'count' referenced before assignment\n",[24,667,668],{"language":26},[28,669,671],{"className":30,"code":670,"language":26,"meta":32,"style":32},"def make_counter():\n    count = 0\n    def increment():\n        nonlocal count   # explicitly binds `count` to the enclosing scope's variable\n        count += 1\n        return count\n    return increment\n\ncounter = make_counter()\nprint(counter())   # 1\nprint(counter())     # 2\nprint(counter())       # 3\n\nother_counter = make_counter()\nprint(other_counter())   # 1 — independent closure, independent state\n",[19,672,673,681,689,697,707,715,721,727,731,739,749,759,769,773,782],{"__ignoreMap":32},[36,674,675,677,679],{"class":38,"line":39},[36,676,69],{"class":68},[36,678,73],{"class":72},[36,680,601],{"class":76},[36,682,683,685,687],{"class":38,"line":46},[36,684,103],{"class":76},[36,686,106],{"class":68},[36,688,610],{"class":80},[36,690,691,693,695],{"class":38,"line":52},[36,692,123],{"class":68},[36,694,126],{"class":72},[36,696,601],{"class":76},[36,698,699,701,704],{"class":38,"line":58},[36,700,140],{"class":68},[36,702,703],{"class":76}," count   ",[36,705,706],{"class":42},"# explicitly binds `count` to the enclosing scope's variable\n",[36,708,709,711,713],{"class":38,"line":65},[36,710,152],{"class":76},[36,712,155],{"class":68},[36,714,158],{"class":80},[36,716,717,719],{"class":38,"line":93},[36,718,164],{"class":68},[36,720,167],{"class":76},[36,722,723,725],{"class":38,"line":100},[36,724,178],{"class":68},[36,726,181],{"class":76},[36,728,729],{"class":38,"line":115},[36,730,62],{"emptyLinePlaceholder":61},[36,732,733,735,737],{"class":38,"line":120},[36,734,655],{"class":76},[36,736,106],{"class":68},[36,738,660],{"class":76},[36,740,741,743,746],{"class":38,"line":137},[36,742,229],{"class":80},[36,744,745],{"class":76},"(counter())   ",[36,747,748],{"class":42},"# 1\n",[36,750,751,753,756],{"class":38,"line":149},[36,752,229],{"class":80},[36,754,755],{"class":76},"(counter())     ",[36,757,758],{"class":42},"# 2\n",[36,760,761,763,766],{"class":38,"line":161},[36,762,229],{"class":80},[36,764,765],{"class":76},"(counter())       ",[36,767,768],{"class":42},"# 3\n",[36,770,771],{"class":38,"line":170},[36,772,62],{"emptyLinePlaceholder":61},[36,774,775,778,780],{"class":38,"line":175},[36,776,777],{"class":76},"other_counter ",[36,779,106],{"class":68},[36,781,660],{"class":76},[36,783,784,786,789],{"class":38,"line":184},[36,785,229],{"class":80},[36,787,788],{"class":76},"(other_counter())   ",[36,790,791],{"class":42},"# 1 — independent closure, independent state\n",[575,793,794,796,797,800,801,803],{},[19,795,572],{}," looks outward through enclosing function scopes (not the module\u002Fglobal scope — that's what ",[19,798,799],{},"global"," is for) and requires the name to already exist in one of those enclosing scopes, unlike ",[19,802,799],{},", which will happily create a new module-level name.",[14,805,807],{"id":806},"the-late-binding-closure-trap","The Late-Binding Closure Trap",[24,809,810],{"language":26},[28,811,813],{"className":30,"code":812,"language":26,"meta":32,"style":32},"# WRONG — every closure captures the SAME variable `i`, not its value at creation time\ncallbacks = []\nfor i in range(3):\n    callbacks.append(lambda: i)\n\nprint([cb() for cb in callbacks])   # [2, 2, 2] — NOT [0, 1, 2]!\n",[19,814,815,820,828,844,853,857],{"__ignoreMap":32},[36,816,817],{"class":38,"line":39},[36,818,819],{"class":42},"# WRONG — every closure captures the SAME variable `i`, not its value at creation time\n",[36,821,822,824,826],{"class":38,"line":46},[36,823,327],{"class":76},[36,825,106],{"class":68},[36,827,332],{"class":76},[36,829,830,832,834,836,838,840,842],{"class":38,"line":52},[36,831,338],{"class":68},[36,833,341],{"class":76},[36,835,344],{"class":68},[36,837,347],{"class":80},[36,839,350],{"class":76},[36,841,353],{"class":80},[36,843,90],{"class":76},[36,845,846,848,850],{"class":38,"line":58},[36,847,361],{"class":76},[36,849,364],{"class":68},[36,851,852],{"class":76},": i)\n",[36,854,855],{"class":38,"line":65},[36,856,62],{"emptyLinePlaceholder":61},[36,858,859,861,863,865,867,869,872],{"class":38,"line":93},[36,860,229],{"class":80},[36,862,383],{"class":76},[36,864,338],{"class":68},[36,866,388],{"class":76},[36,868,344],{"class":68},[36,870,871],{"class":76}," callbacks])   ",[36,873,874],{"class":42},"# [2, 2, 2] — NOT [0, 1, 2]!\n",[575,876,877,878,881,882,886,887,890,891,894],{},"Closures capture ",[579,879,880],{},"variables by reference",", not values by copy. By the time any callback is ",[883,884,885],"em",{},"called",", the loop has finished and ",[19,888,889],{},"i"," holds its final value (",[19,892,893],{},"2",") — all three lambdas share that one cell.",[24,896,897],{"language":26},[28,898,900],{"className":30,"code":899,"language":26,"meta":32,"style":32},"# RIGHT — default argument evaluated ONCE, at function-definition time, per iteration\ncallbacks_fixed = []\nfor i in range(3):\n    callbacks_fixed.append(lambda i=i: i)   # binds the CURRENT value of i as a default\n\nprint([cb() for cb in callbacks_fixed])   # [0, 1, 2] — correct\n\n# RIGHT (alternative) — a factory function creates a genuinely new scope per call\ndef make_callback(i):\n    return lambda: i\n\ncallbacks_factory = [make_callback(i) for i in range(3)]\nprint([cb() for cb in callbacks_factory])   # [0, 1, 2] — correct\n",[19,901,902,907,915,931,946,950,967,971,976,985,994,998,1020],{"__ignoreMap":32},[36,903,904],{"class":38,"line":39},[36,905,906],{"class":42},"# RIGHT — default argument evaluated ONCE, at function-definition time, per iteration\n",[36,908,909,911,913],{"class":38,"line":46},[36,910,413],{"class":76},[36,912,106],{"class":68},[36,914,332],{"class":76},[36,916,917,919,921,923,925,927,929],{"class":38,"line":52},[36,918,338],{"class":68},[36,920,341],{"class":76},[36,922,344],{"class":68},[36,924,347],{"class":80},[36,926,350],{"class":76},[36,928,353],{"class":80},[36,930,90],{"class":76},[36,932,933,935,937,939,941,943],{"class":38,"line":58},[36,934,440],{"class":76},[36,936,364],{"class":68},[36,938,445],{"class":76},[36,940,106],{"class":68},[36,942,450],{"class":76},[36,944,945],{"class":42},"# binds the CURRENT value of i as a default\n",[36,947,948],{"class":38,"line":65},[36,949,62],{"emptyLinePlaceholder":61},[36,951,952,954,956,958,960,962,965],{"class":38,"line":93},[36,953,229],{"class":80},[36,955,383],{"class":76},[36,957,338],{"class":68},[36,959,388],{"class":76},[36,961,344],{"class":68},[36,963,964],{"class":76}," callbacks_fixed])   ",[36,966,477],{"class":42},[36,968,969],{"class":38,"line":100},[36,970,62],{"emptyLinePlaceholder":61},[36,972,973],{"class":38,"line":115},[36,974,975],{"class":42},"# RIGHT (alternative) — a factory function creates a genuinely new scope per call\n",[36,977,978,980,982],{"class":38,"line":120},[36,979,69],{"class":68},[36,981,496],{"class":72},[36,983,984],{"class":76},"(i):\n",[36,986,987,989,991],{"class":38,"line":137},[36,988,178],{"class":68},[36,990,511],{"class":68},[36,992,993],{"class":76},": i\n",[36,995,996],{"class":38,"line":149},[36,997,62],{"emptyLinePlaceholder":61},[36,999,1000,1002,1004,1006,1008,1010,1012,1014,1016,1018],{"class":38,"line":161},[36,1001,528],{"class":76},[36,1003,106],{"class":68},[36,1005,533],{"class":76},[36,1007,338],{"class":68},[36,1009,341],{"class":76},[36,1011,344],{"class":68},[36,1013,347],{"class":80},[36,1015,350],{"class":76},[36,1017,353],{"class":80},[36,1019,548],{"class":76},[36,1021,1022,1024,1026,1028,1030,1032,1035],{"class":38,"line":170},[36,1023,229],{"class":80},[36,1025,383],{"class":76},[36,1027,338],{"class":68},[36,1029,388],{"class":76},[36,1031,344],{"class":68},[36,1033,1034],{"class":76}," callbacks_factory])   ",[36,1036,477],{"class":42},[575,1038,1039],{},"This is one of Python's most common interview-gotcha topics, and it bites in real code most often with GUI event handlers and async callbacks registered inside a loop.",[14,1041,1043],{"id":1042},"decorators-functions-that-wrap-functions","Decorators: Functions That Wrap Functions",[575,1045,1046,1047,1050,1051,1053],{},"A decorator is a callable that takes a function and returns a (usually different) callable — ",[19,1048,1049],{},"@decorator"," above a ",[19,1052,69],{}," is pure syntactic sugar.",[24,1055,1056],{"language":26},[28,1057,1059],{"className":30,"code":1058,"language":26,"meta":32,"style":32},"def shout(func):\n    def wrapper(*args, **kwargs):\n        result = func(*args, **kwargs)\n        return result.upper()\n    return wrapper\n\n@shout\ndef greet(name):\n    return f\"hello, {name}\"\n\nprint(greet(\"ada\"))   # HELLO, ADA\n\n# @shout is exactly equivalent to:\ndef greet2(name):\n    return f\"hello, {name}\"\ngreet2 = shout(greet2)\nprint(greet2(\"ada\"))   # HELLO, ADA\n",[19,1060,1061,1071,1092,1111,1118,1125,1129,1134,1144,1166,1170,1186,1190,1195,1204,1220,1230],{"__ignoreMap":32},[36,1062,1063,1065,1068],{"class":38,"line":39},[36,1064,69],{"class":68},[36,1066,1067],{"class":72}," shout",[36,1069,1070],{"class":76},"(func):\n",[36,1072,1073,1075,1078,1080,1083,1086,1089],{"class":38,"line":46},[36,1074,123],{"class":68},[36,1076,1077],{"class":72}," wrapper",[36,1079,350],{"class":76},[36,1081,1082],{"class":68},"*",[36,1084,1085],{"class":76},"args, ",[36,1087,1088],{"class":68},"**",[36,1090,1091],{"class":76},"kwargs):\n",[36,1093,1094,1097,1099,1102,1104,1106,1108],{"class":38,"line":52},[36,1095,1096],{"class":76},"        result ",[36,1098,106],{"class":68},[36,1100,1101],{"class":76}," func(",[36,1103,1082],{"class":68},[36,1105,1085],{"class":76},[36,1107,1088],{"class":68},[36,1109,1110],{"class":76},"kwargs)\n",[36,1112,1113,1115],{"class":38,"line":58},[36,1114,164],{"class":68},[36,1116,1117],{"class":76}," result.upper()\n",[36,1119,1120,1122],{"class":38,"line":65},[36,1121,178],{"class":68},[36,1123,1124],{"class":76}," wrapper\n",[36,1126,1127],{"class":38,"line":93},[36,1128,62],{"emptyLinePlaceholder":61},[36,1130,1131],{"class":38,"line":100},[36,1132,1133],{"class":72},"@shout\n",[36,1135,1136,1138,1141],{"class":38,"line":115},[36,1137,69],{"class":68},[36,1139,1140],{"class":72}," greet",[36,1142,1143],{"class":76},"(name):\n",[36,1145,1146,1148,1151,1154,1157,1160,1163],{"class":38,"line":120},[36,1147,178],{"class":68},[36,1149,1150],{"class":68}," f",[36,1152,1153],{"class":96},"\"hello, ",[36,1155,1156],{"class":80},"{",[36,1158,1159],{"class":76},"name",[36,1161,1162],{"class":80},"}",[36,1164,1165],{"class":96},"\"\n",[36,1167,1168],{"class":38,"line":137},[36,1169,62],{"emptyLinePlaceholder":61},[36,1171,1172,1174,1177,1180,1183],{"class":38,"line":149},[36,1173,229],{"class":80},[36,1175,1176],{"class":76},"(greet(",[36,1178,1179],{"class":96},"\"ada\"",[36,1181,1182],{"class":76},"))   ",[36,1184,1185],{"class":42},"# HELLO, ADA\n",[36,1187,1188],{"class":38,"line":161},[36,1189,62],{"emptyLinePlaceholder":61},[36,1191,1192],{"class":38,"line":170},[36,1193,1194],{"class":42},"# @shout is exactly equivalent to:\n",[36,1196,1197,1199,1202],{"class":38,"line":175},[36,1198,69],{"class":68},[36,1200,1201],{"class":72}," greet2",[36,1203,1143],{"class":76},[36,1205,1206,1208,1210,1212,1214,1216,1218],{"class":38,"line":184},[36,1207,178],{"class":68},[36,1209,1150],{"class":68},[36,1211,1153],{"class":96},[36,1213,1156],{"class":80},[36,1215,1159],{"class":76},[36,1217,1162],{"class":80},[36,1219,1165],{"class":96},[36,1221,1222,1225,1227],{"class":38,"line":189},[36,1223,1224],{"class":76},"greet2 ",[36,1226,106],{"class":68},[36,1228,1229],{"class":76}," shout(greet2)\n",[36,1231,1232,1234,1237,1239,1241],{"class":38,"line":206},[36,1233,229],{"class":80},[36,1235,1236],{"class":76},"(greet2(",[36,1238,1179],{"class":96},[36,1240,1182],{"class":76},[36,1242,1185],{"class":42},[1244,1245,1247],"h3",{"id":1246},"a-real-world-timing-decorator","A real-world timing decorator",[24,1249,1250],{"language":26},[28,1251,1253],{"className":30,"code":1252,"language":26,"meta":32,"style":32},"import time\nfrom functools import wraps\n\ndef timed(func):\n    @wraps(func)                   # preserves __name__, __doc__ — see chapter 10\n    def wrapper(*args, **kwargs):\n        start = time.perf_counter()\n        try:\n            return func(*args, **kwargs)\n        finally:\n            elapsed = time.perf_counter() - start\n            print(f\"{func.__name__} took {elapsed:.4f}s\")\n    return wrapper\n\n@timed\ndef slow_sum(n):\n    return sum(range(n))\n\nslow_sum(10_000_000)   # slow_sum took 0.1234s\n",[19,1254,1255,1263,1276,1280,1289,1300,1316,1326,1333,1348,1355,1371,1410,1416,1420,1425,1435,1450,1454],{"__ignoreMap":32},[36,1256,1257,1260],{"class":38,"line":39},[36,1258,1259],{"class":68},"import",[36,1261,1262],{"class":76}," time\n",[36,1264,1265,1268,1271,1273],{"class":38,"line":46},[36,1266,1267],{"class":68},"from",[36,1269,1270],{"class":76}," functools ",[36,1272,1259],{"class":68},[36,1274,1275],{"class":76}," wraps\n",[36,1277,1278],{"class":38,"line":52},[36,1279,62],{"emptyLinePlaceholder":61},[36,1281,1282,1284,1287],{"class":38,"line":58},[36,1283,69],{"class":68},[36,1285,1286],{"class":72}," timed",[36,1288,1070],{"class":76},[36,1290,1291,1294,1297],{"class":38,"line":65},[36,1292,1293],{"class":72},"    @wraps",[36,1295,1296],{"class":76},"(func)                   ",[36,1298,1299],{"class":42},"# preserves __name__, __doc__ — see chapter 10\n",[36,1301,1302,1304,1306,1308,1310,1312,1314],{"class":38,"line":93},[36,1303,123],{"class":68},[36,1305,1077],{"class":72},[36,1307,350],{"class":76},[36,1309,1082],{"class":68},[36,1311,1085],{"class":76},[36,1313,1088],{"class":68},[36,1315,1091],{"class":76},[36,1317,1318,1321,1323],{"class":38,"line":100},[36,1319,1320],{"class":76},"        start ",[36,1322,106],{"class":68},[36,1324,1325],{"class":76}," time.perf_counter()\n",[36,1327,1328,1331],{"class":38,"line":115},[36,1329,1330],{"class":68},"        try",[36,1332,134],{"class":76},[36,1334,1335,1338,1340,1342,1344,1346],{"class":38,"line":120},[36,1336,1337],{"class":68},"            return",[36,1339,1101],{"class":76},[36,1341,1082],{"class":68},[36,1343,1085],{"class":76},[36,1345,1088],{"class":68},[36,1347,1110],{"class":76},[36,1349,1350,1353],{"class":38,"line":137},[36,1351,1352],{"class":68},"        finally",[36,1354,134],{"class":76},[36,1356,1357,1360,1362,1365,1368],{"class":38,"line":149},[36,1358,1359],{"class":76},"            elapsed ",[36,1361,106],{"class":68},[36,1363,1364],{"class":76}," time.perf_counter() ",[36,1366,1367],{"class":68},"-",[36,1369,1370],{"class":76}," start\n",[36,1372,1373,1376,1378,1381,1384,1386,1389,1392,1395,1397,1400,1403,1405,1408],{"class":38,"line":161},[36,1374,1375],{"class":80},"            print",[36,1377,350],{"class":76},[36,1379,1380],{"class":68},"f",[36,1382,1383],{"class":96},"\"",[36,1385,1156],{"class":80},[36,1387,1388],{"class":76},"func.",[36,1390,1391],{"class":80},"__name__}",[36,1393,1394],{"class":96}," took ",[36,1396,1156],{"class":80},[36,1398,1399],{"class":76},"elapsed",[36,1401,1402],{"class":68},":.4f",[36,1404,1162],{"class":80},[36,1406,1407],{"class":96},"s\"",[36,1409,203],{"class":76},[36,1411,1412,1414],{"class":38,"line":170},[36,1413,178],{"class":68},[36,1415,1124],{"class":76},[36,1417,1418],{"class":38,"line":175},[36,1419,62],{"emptyLinePlaceholder":61},[36,1421,1422],{"class":38,"line":184},[36,1423,1424],{"class":72},"@timed\n",[36,1426,1427,1429,1432],{"class":38,"line":189},[36,1428,69],{"class":68},[36,1430,1431],{"class":72}," slow_sum",[36,1433,1434],{"class":76},"(n):\n",[36,1436,1437,1439,1442,1444,1447],{"class":38,"line":206},[36,1438,178],{"class":68},[36,1440,1441],{"class":80}," sum",[36,1443,350],{"class":76},[36,1445,1446],{"class":80},"range",[36,1448,1449],{"class":76},"(n))\n",[36,1451,1452],{"class":38,"line":221},[36,1453,62],{"emptyLinePlaceholder":61},[36,1455,1456,1459,1462,1465],{"class":38,"line":226},[36,1457,1458],{"class":76},"slow_sum(",[36,1460,1461],{"class":80},"10_000_000",[36,1463,1464],{"class":76},")   ",[36,1466,1467],{"class":42},"# slow_sum took 0.1234s\n",[575,1469,1470,1471,1474,1475,1478,1479,1482],{},"The ",[19,1472,1473],{},"try","\u002F",[19,1476,1477],{},"finally"," ensures the timing is logged even if ",[19,1480,1481],{},"func"," raises — a decorator that only measures on the happy path silently hides timing information for the calls that matter most (the ones that fail or take unexpectedly long before erroring).",[14,1484,1486],{"id":1485},"decorators-that-take-arguments","Decorators That Take Arguments",[575,1488,1489,1490,1493],{},"A parameterized decorator needs ",[579,1491,1492],{},"three"," levels of nesting: an outer function that accepts the decorator's own arguments, which returns the actual decorator, which returns the wrapper.",[24,1495,1496],{"language":26},[28,1497,1499],{"className":30,"code":1498,"language":26,"meta":32,"style":32},"from functools import wraps\n\ndef retry(times=3, exceptions=(Exception,)):\n    def decorator(func):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            last_exc = None\n            for attempt in range(1, times + 1):\n                try:\n                    return func(*args, **kwargs)\n                except exceptions as exc:\n                    last_exc = exc\n                    print(f\"Attempt {attempt} failed: {exc}\")\n            raise last_exc\n        return wrapper\n    return decorator\n\n@retry(times=3, exceptions=(ConnectionError,))\ndef fetch_data():\n    import random\n    if random.random() \u003C 0.7:\n        raise ConnectionError(\"timeout\")\n    return \"data\"\n\n# fetch_data()   # retries up to 3 times, then re-raises if still failing\n",[19,1500,1501,1511,1515,1542,1551,1559,1576,1586,1613,1620,1635,1649,1659,1692,1700,1706,1713,1717,1748,1757,1765,1781,1796,1803,1807],{"__ignoreMap":32},[36,1502,1503,1505,1507,1509],{"class":38,"line":39},[36,1504,1267],{"class":68},[36,1506,1270],{"class":76},[36,1508,1259],{"class":68},[36,1510,1275],{"class":76},[36,1512,1513],{"class":38,"line":46},[36,1514,62],{"emptyLinePlaceholder":61},[36,1516,1517,1519,1522,1525,1527,1529,1532,1534,1536,1539],{"class":38,"line":52},[36,1518,69],{"class":68},[36,1520,1521],{"class":72}," retry",[36,1523,1524],{"class":76},"(times",[36,1526,106],{"class":68},[36,1528,353],{"class":80},[36,1530,1531],{"class":76},", exceptions",[36,1533,106],{"class":68},[36,1535,350],{"class":76},[36,1537,1538],{"class":80},"Exception",[36,1540,1541],{"class":76},",)):\n",[36,1543,1544,1546,1549],{"class":38,"line":58},[36,1545,123],{"class":68},[36,1547,1548],{"class":72}," decorator",[36,1550,1070],{"class":76},[36,1552,1553,1556],{"class":38,"line":65},[36,1554,1555],{"class":72},"        @wraps",[36,1557,1558],{"class":76},"(func)\n",[36,1560,1561,1564,1566,1568,1570,1572,1574],{"class":38,"line":93},[36,1562,1563],{"class":68},"        def",[36,1565,1077],{"class":72},[36,1567,350],{"class":76},[36,1569,1082],{"class":68},[36,1571,1085],{"class":76},[36,1573,1088],{"class":68},[36,1575,1091],{"class":76},[36,1577,1578,1581,1583],{"class":38,"line":100},[36,1579,1580],{"class":76},"            last_exc ",[36,1582,106],{"class":68},[36,1584,1585],{"class":80}," None\n",[36,1587,1588,1591,1594,1596,1598,1600,1603,1606,1609,1611],{"class":38,"line":115},[36,1589,1590],{"class":68},"            for",[36,1592,1593],{"class":76}," attempt ",[36,1595,344],{"class":68},[36,1597,347],{"class":80},[36,1599,350],{"class":76},[36,1601,1602],{"class":80},"1",[36,1604,1605],{"class":76},", times ",[36,1607,1608],{"class":68},"+",[36,1610,627],{"class":80},[36,1612,90],{"class":76},[36,1614,1615,1618],{"class":38,"line":120},[36,1616,1617],{"class":68},"                try",[36,1619,134],{"class":76},[36,1621,1622,1625,1627,1629,1631,1633],{"class":38,"line":137},[36,1623,1624],{"class":68},"                    return",[36,1626,1101],{"class":76},[36,1628,1082],{"class":68},[36,1630,1085],{"class":76},[36,1632,1088],{"class":68},[36,1634,1110],{"class":76},[36,1636,1637,1640,1643,1646],{"class":38,"line":149},[36,1638,1639],{"class":68},"                except",[36,1641,1642],{"class":76}," exceptions ",[36,1644,1645],{"class":68},"as",[36,1647,1648],{"class":76}," exc:\n",[36,1650,1651,1654,1656],{"class":38,"line":161},[36,1652,1653],{"class":76},"                    last_exc ",[36,1655,106],{"class":68},[36,1657,1658],{"class":76}," exc\n",[36,1660,1661,1664,1666,1668,1671,1673,1676,1678,1681,1683,1686,1688,1690],{"class":38,"line":170},[36,1662,1663],{"class":80},"                    print",[36,1665,350],{"class":76},[36,1667,1380],{"class":68},[36,1669,1670],{"class":96},"\"Attempt ",[36,1672,1156],{"class":80},[36,1674,1675],{"class":76},"attempt",[36,1677,1162],{"class":80},[36,1679,1680],{"class":96}," failed: ",[36,1682,1156],{"class":80},[36,1684,1685],{"class":76},"exc",[36,1687,1162],{"class":80},[36,1689,1383],{"class":96},[36,1691,203],{"class":76},[36,1693,1694,1697],{"class":38,"line":175},[36,1695,1696],{"class":68},"            raise",[36,1698,1699],{"class":76}," last_exc\n",[36,1701,1702,1704],{"class":38,"line":184},[36,1703,164],{"class":68},[36,1705,1124],{"class":76},[36,1707,1708,1710],{"class":38,"line":189},[36,1709,178],{"class":68},[36,1711,1712],{"class":76}," decorator\n",[36,1714,1715],{"class":38,"line":206},[36,1716,62],{"emptyLinePlaceholder":61},[36,1718,1719,1722,1724,1728,1730,1732,1735,1738,1740,1742,1745],{"class":38,"line":221},[36,1720,1721],{"class":72},"@retry",[36,1723,350],{"class":76},[36,1725,1727],{"class":1726},"sCrzJ","times",[36,1729,106],{"class":68},[36,1731,353],{"class":80},[36,1733,1734],{"class":76},", ",[36,1736,1737],{"class":1726},"exceptions",[36,1739,106],{"class":68},[36,1741,350],{"class":76},[36,1743,1744],{"class":80},"ConnectionError",[36,1746,1747],{"class":76},",))\n",[36,1749,1750,1752,1755],{"class":38,"line":226},[36,1751,69],{"class":68},[36,1753,1754],{"class":72}," fetch_data",[36,1756,601],{"class":76},[36,1758,1759,1762],{"class":38,"line":238},[36,1760,1761],{"class":68},"    import",[36,1763,1764],{"class":76}," random\n",[36,1766,1767,1770,1773,1776,1779],{"class":38,"line":248},[36,1768,1769],{"class":68},"    if",[36,1771,1772],{"class":76}," random.random() ",[36,1774,1775],{"class":68},"\u003C",[36,1777,1778],{"class":80}," 0.7",[36,1780,134],{"class":76},[36,1782,1783,1786,1789,1791,1794],{"class":38,"line":259},[36,1784,1785],{"class":68},"        raise",[36,1787,1788],{"class":80}," ConnectionError",[36,1790,350],{"class":76},[36,1792,1793],{"class":96},"\"timeout\"",[36,1795,203],{"class":76},[36,1797,1798,1800],{"class":38,"line":264},[36,1799,178],{"class":68},[36,1801,1802],{"class":96}," \"data\"\n",[36,1804,1805],{"class":38,"line":270},[36,1806,62],{"emptyLinePlaceholder":61},[36,1808,1809],{"class":38,"line":286},[36,1810,1811],{"class":42},"# fetch_data()   # retries up to 3 times, then re-raises if still failing\n",[575,1813,1814,1817,1818,1821,1822,1825,1826,1829,1830,1833,1834,1836,1837,1840,1841,1843,1844,1847],{},[19,1815,1816],{},"@retry(times=3, ...)"," first calls ",[19,1819,1820],{},"retry(times=3, ...)",", which returns ",[19,1823,1824],{},"decorator","; ",[579,1827,1828],{},"that"," is what actually gets applied to ",[19,1831,1832],{},"fetch_data",". Forgetting the extra call — writing ",[19,1835,1721],{}," instead of ",[19,1838,1839],{},"@retry()"," when the decorator is written to always take arguments — passes the function itself as ",[19,1842,1727],{},", producing a confusing ",[19,1845,1846],{},"TypeError"," deep inside the decorator body rather than at the call site.",[1244,1849,1851],{"id":1850},"making-the-parentheses-optional-advanced","Making the parentheses optional (advanced)",[24,1853,1854],{"language":26},[28,1855,1857],{"className":30,"code":1856,"language":26,"meta":32,"style":32},"from functools import wraps\n\ndef log_calls(func=None, *, prefix=\"CALL\"):\n    def decorator(f):\n        @wraps(f)\n        def wrapper(*args, **kwargs):\n            print(f\"{prefix}: {f.__name__}({args}, {kwargs})\")\n            return f(*args, **kwargs)\n        return wrapper\n    if func is None:            # used as @log_calls(prefix=\"...\")\n        return decorator\n    return decorator(func)        # used as @log_calls with no parens\n\n@log_calls\ndef add(a, b):\n    return a + b\n\n@log_calls(prefix=\"DEBUG\")\ndef sub(a, b):\n    return a - b\n\nadd(2, 3)     # CALL: add((2, 3), {})\nsub(5, 1)       # DEBUG: sub((5, 1), {})\n",[19,1858,1859,1869,1873,1902,1911,1918,1934,1984,1999,2005,2024,2030,2040,2044,2049,2059,2071,2075,2091,2100,2110,2114,2131],{"__ignoreMap":32},[36,1860,1861,1863,1865,1867],{"class":38,"line":39},[36,1862,1267],{"class":68},[36,1864,1270],{"class":76},[36,1866,1259],{"class":68},[36,1868,1275],{"class":76},[36,1870,1871],{"class":38,"line":46},[36,1872,62],{"emptyLinePlaceholder":61},[36,1874,1875,1877,1880,1883,1885,1888,1890,1892,1895,1897,1900],{"class":38,"line":52},[36,1876,69],{"class":68},[36,1878,1879],{"class":72}," log_calls",[36,1881,1882],{"class":76},"(func",[36,1884,106],{"class":68},[36,1886,1887],{"class":80},"None",[36,1889,1734],{"class":76},[36,1891,1082],{"class":68},[36,1893,1894],{"class":76},", prefix",[36,1896,106],{"class":68},[36,1898,1899],{"class":96},"\"CALL\"",[36,1901,90],{"class":76},[36,1903,1904,1906,1908],{"class":38,"line":58},[36,1905,123],{"class":68},[36,1907,1548],{"class":72},[36,1909,1910],{"class":76},"(f):\n",[36,1912,1913,1915],{"class":38,"line":65},[36,1914,1555],{"class":72},[36,1916,1917],{"class":76},"(f)\n",[36,1919,1920,1922,1924,1926,1928,1930,1932],{"class":38,"line":93},[36,1921,1563],{"class":68},[36,1923,1077],{"class":72},[36,1925,350],{"class":76},[36,1927,1082],{"class":68},[36,1929,1085],{"class":76},[36,1931,1088],{"class":68},[36,1933,1091],{"class":76},[36,1935,1936,1938,1940,1942,1944,1946,1949,1951,1954,1956,1959,1961,1963,1965,1968,1970,1972,1974,1977,1979,1982],{"class":38,"line":100},[36,1937,1375],{"class":80},[36,1939,350],{"class":76},[36,1941,1380],{"class":68},[36,1943,1383],{"class":96},[36,1945,1156],{"class":80},[36,1947,1948],{"class":76},"prefix",[36,1950,1162],{"class":80},[36,1952,1953],{"class":96},": ",[36,1955,1156],{"class":80},[36,1957,1958],{"class":76},"f.",[36,1960,1391],{"class":80},[36,1962,350],{"class":96},[36,1964,1156],{"class":80},[36,1966,1967],{"class":76},"args",[36,1969,1162],{"class":80},[36,1971,1734],{"class":96},[36,1973,1156],{"class":80},[36,1975,1976],{"class":76},"kwargs",[36,1978,1162],{"class":80},[36,1980,1981],{"class":96},")\"",[36,1983,203],{"class":76},[36,1985,1986,1988,1991,1993,1995,1997],{"class":38,"line":115},[36,1987,1337],{"class":68},[36,1989,1990],{"class":76}," f(",[36,1992,1082],{"class":68},[36,1994,1085],{"class":76},[36,1996,1088],{"class":68},[36,1998,1110],{"class":76},[36,2000,2001,2003],{"class":38,"line":120},[36,2002,164],{"class":68},[36,2004,1124],{"class":76},[36,2006,2007,2009,2012,2015,2018,2021],{"class":38,"line":137},[36,2008,1769],{"class":68},[36,2010,2011],{"class":76}," func ",[36,2013,2014],{"class":68},"is",[36,2016,2017],{"class":80}," None",[36,2019,2020],{"class":76},":            ",[36,2022,2023],{"class":42},"# used as @log_calls(prefix=\"...\")\n",[36,2025,2026,2028],{"class":38,"line":149},[36,2027,164],{"class":68},[36,2029,1712],{"class":76},[36,2031,2032,2034,2037],{"class":38,"line":161},[36,2033,178],{"class":68},[36,2035,2036],{"class":76}," decorator(func)        ",[36,2038,2039],{"class":42},"# used as @log_calls with no parens\n",[36,2041,2042],{"class":38,"line":170},[36,2043,62],{"emptyLinePlaceholder":61},[36,2045,2046],{"class":38,"line":175},[36,2047,2048],{"class":72},"@log_calls\n",[36,2050,2051,2053,2056],{"class":38,"line":184},[36,2052,69],{"class":68},[36,2054,2055],{"class":72}," add",[36,2057,2058],{"class":76},"(a, b):\n",[36,2060,2061,2063,2066,2068],{"class":38,"line":189},[36,2062,178],{"class":68},[36,2064,2065],{"class":76}," a ",[36,2067,1608],{"class":68},[36,2069,2070],{"class":76}," b\n",[36,2072,2073],{"class":38,"line":206},[36,2074,62],{"emptyLinePlaceholder":61},[36,2076,2077,2080,2082,2084,2086,2089],{"class":38,"line":221},[36,2078,2079],{"class":72},"@log_calls",[36,2081,350],{"class":76},[36,2083,1948],{"class":1726},[36,2085,106],{"class":68},[36,2087,2088],{"class":96},"\"DEBUG\"",[36,2090,203],{"class":76},[36,2092,2093,2095,2098],{"class":38,"line":226},[36,2094,69],{"class":68},[36,2096,2097],{"class":72}," sub",[36,2099,2058],{"class":76},[36,2101,2102,2104,2106,2108],{"class":38,"line":238},[36,2103,178],{"class":68},[36,2105,2065],{"class":76},[36,2107,1367],{"class":68},[36,2109,2070],{"class":76},[36,2111,2112],{"class":38,"line":248},[36,2113,62],{"emptyLinePlaceholder":61},[36,2115,2116,2119,2121,2123,2125,2128],{"class":38,"line":259},[36,2117,2118],{"class":76},"add(",[36,2120,893],{"class":80},[36,2122,1734],{"class":76},[36,2124,353],{"class":80},[36,2126,2127],{"class":76},")     ",[36,2129,2130],{"class":42},"# CALL: add((2, 3), {})\n",[36,2132,2133,2136,2139,2141,2143,2146],{"class":38,"line":264},[36,2134,2135],{"class":76},"sub(",[36,2137,2138],{"class":80},"5",[36,2140,1734],{"class":76},[36,2142,1602],{"class":80},[36,2144,2145],{"class":76},")       ",[36,2147,2148],{"class":42},"# DEBUG: sub((5, 1), {})\n",[14,2150,2152],{"id":2151},"stacking-multiple-decorators","Stacking Multiple Decorators",[24,2154,2155],{"language":26},[28,2156,2158],{"className":30,"code":2157,"language":26,"meta":32,"style":32},"def bold(func):\n    def wrapper(*args, **kwargs):\n        return f\"\u003Cb>{func(*args, **kwargs)}\u003C\u002Fb>\"\n    return wrapper\n\ndef italic(func):\n    def wrapper(*args, **kwargs):\n        return f\"\u003Ci>{func(*args, **kwargs)}\u003C\u002Fi>\"\n    return wrapper\n\n@bold\n@italic\ndef text():\n    return \"hello\"\n\nprint(text())   # \u003Cb>\u003Ci>hello\u003C\u002Fi>\u003C\u002Fb>\n",[19,2159,2160,2169,2185,2213,2219,2223,2232,2248,2274,2280,2284,2289,2294,2303,2310,2314],{"__ignoreMap":32},[36,2161,2162,2164,2167],{"class":38,"line":39},[36,2163,69],{"class":68},[36,2165,2166],{"class":72}," bold",[36,2168,1070],{"class":76},[36,2170,2171,2173,2175,2177,2179,2181,2183],{"class":38,"line":46},[36,2172,123],{"class":68},[36,2174,1077],{"class":72},[36,2176,350],{"class":76},[36,2178,1082],{"class":68},[36,2180,1085],{"class":76},[36,2182,1088],{"class":68},[36,2184,1091],{"class":76},[36,2186,2187,2189,2191,2194,2196,2199,2201,2203,2205,2208,2210],{"class":38,"line":52},[36,2188,164],{"class":68},[36,2190,1150],{"class":68},[36,2192,2193],{"class":96},"\"\u003Cb>",[36,2195,1156],{"class":80},[36,2197,2198],{"class":76},"func(",[36,2200,1082],{"class":68},[36,2202,1085],{"class":76},[36,2204,1088],{"class":68},[36,2206,2207],{"class":76},"kwargs)",[36,2209,1162],{"class":80},[36,2211,2212],{"class":96},"\u003C\u002Fb>\"\n",[36,2214,2215,2217],{"class":38,"line":58},[36,2216,178],{"class":68},[36,2218,1124],{"class":76},[36,2220,2221],{"class":38,"line":65},[36,2222,62],{"emptyLinePlaceholder":61},[36,2224,2225,2227,2230],{"class":38,"line":93},[36,2226,69],{"class":68},[36,2228,2229],{"class":72}," italic",[36,2231,1070],{"class":76},[36,2233,2234,2236,2238,2240,2242,2244,2246],{"class":38,"line":100},[36,2235,123],{"class":68},[36,2237,1077],{"class":72},[36,2239,350],{"class":76},[36,2241,1082],{"class":68},[36,2243,1085],{"class":76},[36,2245,1088],{"class":68},[36,2247,1091],{"class":76},[36,2249,2250,2252,2254,2257,2259,2261,2263,2265,2267,2269,2271],{"class":38,"line":115},[36,2251,164],{"class":68},[36,2253,1150],{"class":68},[36,2255,2256],{"class":96},"\"\u003Ci>",[36,2258,1156],{"class":80},[36,2260,2198],{"class":76},[36,2262,1082],{"class":68},[36,2264,1085],{"class":76},[36,2266,1088],{"class":68},[36,2268,2207],{"class":76},[36,2270,1162],{"class":80},[36,2272,2273],{"class":96},"\u003C\u002Fi>\"\n",[36,2275,2276,2278],{"class":38,"line":120},[36,2277,178],{"class":68},[36,2279,1124],{"class":76},[36,2281,2282],{"class":38,"line":137},[36,2283,62],{"emptyLinePlaceholder":61},[36,2285,2286],{"class":38,"line":149},[36,2287,2288],{"class":72},"@bold\n",[36,2290,2291],{"class":38,"line":161},[36,2292,2293],{"class":72},"@italic\n",[36,2295,2296,2298,2301],{"class":38,"line":170},[36,2297,69],{"class":68},[36,2299,2300],{"class":72}," text",[36,2302,601],{"class":76},[36,2304,2305,2307],{"class":38,"line":175},[36,2306,178],{"class":68},[36,2308,2309],{"class":96}," \"hello\"\n",[36,2311,2312],{"class":38,"line":184},[36,2313,62],{"emptyLinePlaceholder":61},[36,2315,2316,2318,2321],{"class":38,"line":189},[36,2317,229],{"class":80},[36,2319,2320],{"class":76},"(text())   ",[36,2322,2323],{"class":42},"# \u003Cb>\u003Ci>hello\u003C\u002Fi>\u003C\u002Fb>\n",[575,2325,2326,2327,1953,2330,2333,2334,2337,2338,2341,2342,2345],{},"Decorators apply ",[579,2328,2329],{},"bottom-up",[19,2331,2332],{},"italic"," wraps ",[19,2335,2336],{},"text"," first, then ",[19,2339,2340],{},"bold"," wraps the result of that — equivalent to ",[19,2343,2344],{},"bold(italic(text))",". Reading the stack from the function outward (bottom to top) tells you the actual order of wrapping, which is the opposite of the visual top-to-bottom reading order.",[14,2347,2349],{"id":2348},"class-based-decorators","Class-Based Decorators",[575,2351,2352,2353,2356],{},"Any object implementing ",[19,2354,2355],{},"__call__"," can act as a decorator — useful when the decorator needs to hold configurable state across calls.",[24,2358,2359],{"language":26},[28,2360,2362],{"className":30,"code":2361,"language":26,"meta":32,"style":32},"class CountCalls:\n    def __init__(self, func):\n        self.func = func\n        self.calls = 0\n\n    def __call__(self, *args, **kwargs):\n        self.calls += 1\n        print(f\"Call #{self.calls} to {self.func.__name__}\")\n        return self.func(*args, **kwargs)\n\n@CountCalls\ndef say_hi():\n    print(\"hi\")\n\nsay_hi()   # Call #1 to say_hi \\n hi\nsay_hi()     # Call #2 to say_hi \\n hi\nprint(say_hi.calls)   # 2\n",[19,2363,2364,2374,2384,2397,2408,2412,2430,2440,2474,2492,2496,2501,2510,2522,2526,2534,2542],{"__ignoreMap":32},[36,2365,2366,2369,2372],{"class":38,"line":39},[36,2367,2368],{"class":68},"class",[36,2370,2371],{"class":72}," CountCalls",[36,2373,134],{"class":76},[36,2375,2376,2378,2381],{"class":38,"line":46},[36,2377,123],{"class":68},[36,2379,2380],{"class":80}," __init__",[36,2382,2383],{"class":76},"(self, func):\n",[36,2385,2386,2389,2392,2394],{"class":38,"line":52},[36,2387,2388],{"class":80},"        self",[36,2390,2391],{"class":76},".func ",[36,2393,106],{"class":68},[36,2395,2396],{"class":76}," func\n",[36,2398,2399,2401,2404,2406],{"class":38,"line":58},[36,2400,2388],{"class":80},[36,2402,2403],{"class":76},".calls ",[36,2405,106],{"class":68},[36,2407,610],{"class":80},[36,2409,2410],{"class":38,"line":65},[36,2411,62],{"emptyLinePlaceholder":61},[36,2413,2414,2416,2419,2422,2424,2426,2428],{"class":38,"line":93},[36,2415,123],{"class":68},[36,2417,2418],{"class":80}," __call__",[36,2420,2421],{"class":76},"(self, ",[36,2423,1082],{"class":68},[36,2425,1085],{"class":76},[36,2427,1088],{"class":68},[36,2429,1091],{"class":76},[36,2431,2432,2434,2436,2438],{"class":38,"line":100},[36,2433,2388],{"class":80},[36,2435,2403],{"class":76},[36,2437,155],{"class":68},[36,2439,158],{"class":80},[36,2441,2442,2445,2447,2449,2452,2455,2458,2460,2463,2465,2468,2470,2472],{"class":38,"line":115},[36,2443,2444],{"class":80},"        print",[36,2446,350],{"class":76},[36,2448,1380],{"class":68},[36,2450,2451],{"class":96},"\"Call #",[36,2453,2454],{"class":80},"{self",[36,2456,2457],{"class":76},".calls",[36,2459,1162],{"class":80},[36,2461,2462],{"class":96}," to ",[36,2464,2454],{"class":80},[36,2466,2467],{"class":76},".func.",[36,2469,1391],{"class":80},[36,2471,1383],{"class":96},[36,2473,203],{"class":76},[36,2475,2476,2478,2481,2484,2486,2488,2490],{"class":38,"line":120},[36,2477,164],{"class":68},[36,2479,2480],{"class":80}," self",[36,2482,2483],{"class":76},".func(",[36,2485,1082],{"class":68},[36,2487,1085],{"class":76},[36,2489,1088],{"class":68},[36,2491,1110],{"class":76},[36,2493,2494],{"class":38,"line":137},[36,2495,62],{"emptyLinePlaceholder":61},[36,2497,2498],{"class":38,"line":149},[36,2499,2500],{"class":72},"@CountCalls\n",[36,2502,2503,2505,2508],{"class":38,"line":161},[36,2504,69],{"class":68},[36,2506,2507],{"class":72}," say_hi",[36,2509,601],{"class":76},[36,2511,2512,2515,2517,2520],{"class":38,"line":170},[36,2513,2514],{"class":80},"    print",[36,2516,350],{"class":76},[36,2518,2519],{"class":96},"\"hi\"",[36,2521,203],{"class":76},[36,2523,2524],{"class":38,"line":175},[36,2525,62],{"emptyLinePlaceholder":61},[36,2527,2528,2531],{"class":38,"line":184},[36,2529,2530],{"class":76},"say_hi()   ",[36,2532,2533],{"class":42},"# Call #1 to say_hi \\n hi\n",[36,2535,2536,2539],{"class":38,"line":189},[36,2537,2538],{"class":76},"say_hi()     ",[36,2540,2541],{"class":42},"# Call #2 to say_hi \\n hi\n",[36,2543,2544,2546,2549],{"class":38,"line":206},[36,2545,229],{"class":80},[36,2547,2548],{"class":76},"(say_hi.calls)   ",[36,2550,758],{"class":42},[575,2552,2553,2554,2557,2558,2561,2562,2565,2566,2569,2570,2573,2574,2577,2578,2581,2582,2585],{},"Note that ",[19,2555,2556],{},"@CountCalls"," replaces ",[19,2559,2560],{},"say_hi"," with an ",[579,2563,2564],{},"instance"," of ",[19,2567,2568],{},"CountCalls",", not a plain function — introspection tools expecting ",[19,2571,2572],{},"say_hi.__name__"," will fail unless you manually copy metadata (",[19,2575,2576],{},"functools.update_wrapper(self, func)"," in ",[19,2579,2580],{},"__init__","), since ",[19,2583,2584],{},"@wraps"," is designed for function-based wrappers.",[14,2587,2589],{"id":2588},"decorating-classes","Decorating Classes",[575,2591,2592],{},"Decorators aren't limited to functions — a decorator applied to a class receives the class object itself and can inspect, modify, or wrap it.",[24,2594,2595],{"language":26},[28,2596,2598],{"className":30,"code":2597,"language":26,"meta":32,"style":32},"def add_repr(cls):\n    def __repr__(self):\n        attrs = \", \".join(f\"{k}={v!r}\" for k, v in vars(self).items())\n        return f\"{cls.__name__}({attrs})\"\n    cls.__repr__ = __repr__\n    return cls\n\n@add_repr\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\nprint(Point(1, 2))   # Point(x=1, y=2)\n",[19,2599,2600,2610,2620,2677,2705,2720,2727,2731,2736,2745,2754,2766,2778,2782],{"__ignoreMap":32},[36,2601,2602,2604,2607],{"class":38,"line":39},[36,2603,69],{"class":68},[36,2605,2606],{"class":72}," add_repr",[36,2608,2609],{"class":76},"(cls):\n",[36,2611,2612,2614,2617],{"class":38,"line":46},[36,2613,123],{"class":68},[36,2615,2616],{"class":80}," __repr__",[36,2618,2619],{"class":76},"(self):\n",[36,2621,2622,2625,2627,2630,2633,2635,2637,2639,2642,2644,2646,2648,2651,2654,2656,2658,2661,2664,2666,2669,2671,2674],{"class":38,"line":52},[36,2623,2624],{"class":76},"        attrs ",[36,2626,106],{"class":68},[36,2628,2629],{"class":96}," \", \"",[36,2631,2632],{"class":76},".join(",[36,2634,1380],{"class":68},[36,2636,1383],{"class":96},[36,2638,1156],{"class":80},[36,2640,2641],{"class":76},"k",[36,2643,1162],{"class":80},[36,2645,106],{"class":96},[36,2647,1156],{"class":80},[36,2649,2650],{"class":76},"v",[36,2652,2653],{"class":68},"!r",[36,2655,1162],{"class":80},[36,2657,1383],{"class":96},[36,2659,2660],{"class":68}," for",[36,2662,2663],{"class":76}," k, v ",[36,2665,344],{"class":68},[36,2667,2668],{"class":80}," vars",[36,2670,350],{"class":76},[36,2672,2673],{"class":80},"self",[36,2675,2676],{"class":76},").items())\n",[36,2678,2679,2681,2683,2685,2688,2691,2693,2695,2697,2700,2702],{"class":38,"line":58},[36,2680,164],{"class":68},[36,2682,1150],{"class":68},[36,2684,1383],{"class":96},[36,2686,2687],{"class":80},"{cls",[36,2689,2690],{"class":76},".",[36,2692,1391],{"class":80},[36,2694,350],{"class":96},[36,2696,1156],{"class":80},[36,2698,2699],{"class":76},"attrs",[36,2701,1162],{"class":80},[36,2703,2704],{"class":96},")\"\n",[36,2706,2707,2710,2712,2715,2717],{"class":38,"line":65},[36,2708,2709],{"class":80},"    cls",[36,2711,2690],{"class":76},[36,2713,2714],{"class":80},"__repr__",[36,2716,84],{"class":68},[36,2718,2719],{"class":80}," __repr__\n",[36,2721,2722,2724],{"class":38,"line":93},[36,2723,178],{"class":68},[36,2725,2726],{"class":80}," cls\n",[36,2728,2729],{"class":38,"line":100},[36,2730,62],{"emptyLinePlaceholder":61},[36,2732,2733],{"class":38,"line":115},[36,2734,2735],{"class":72},"@add_repr\n",[36,2737,2738,2740,2743],{"class":38,"line":120},[36,2739,2368],{"class":68},[36,2741,2742],{"class":72}," Point",[36,2744,134],{"class":76},[36,2746,2747,2749,2751],{"class":38,"line":137},[36,2748,123],{"class":68},[36,2750,2380],{"class":80},[36,2752,2753],{"class":76},"(self, x, y):\n",[36,2755,2756,2758,2761,2763],{"class":38,"line":149},[36,2757,2388],{"class":80},[36,2759,2760],{"class":76},".x ",[36,2762,106],{"class":68},[36,2764,2765],{"class":76}," x\n",[36,2767,2768,2770,2773,2775],{"class":38,"line":161},[36,2769,2388],{"class":80},[36,2771,2772],{"class":76},".y ",[36,2774,106],{"class":68},[36,2776,2777],{"class":76}," y\n",[36,2779,2780],{"class":38,"line":170},[36,2781,62],{"emptyLinePlaceholder":61},[36,2783,2784,2786,2789,2791,2793,2795,2797],{"class":38,"line":175},[36,2785,229],{"class":80},[36,2787,2788],{"class":76},"(Point(",[36,2790,1602],{"class":80},[36,2792,1734],{"class":76},[36,2794,893],{"class":80},[36,2796,1182],{"class":76},[36,2798,2799],{"class":42},"# Point(x=1, y=2)\n",[575,2801,2802,2803,2806],{},"This pattern — and the more powerful ",[19,2804,2805],{},"dataclasses.dataclass",", which is itself a class decorator — is covered in depth alongside metaclasses in chapter 23.",[14,2808,2810],{"id":2809},"tips-tricks","💡 Tips & Tricks",[2812,2813,2814,2833,2850,2866,2874],"ul",{},[2815,2816,2817,2820,2821,2824,2825,2828,2829,2832],"li",{},[579,2818,2819],{},"Idiom",": use ",[19,2822,2823],{},"functools.reduce","-style thinking to understand stacked decorators — ",[19,2826,2827],{},"@a @b @c def f"," is ",[19,2830,2831],{},"f = a(b(c(f)))","; if the order of side effects (logging, timing, caching) matters, write it out this way before debugging unexpected behavior.",[2815,2834,2835,1953,2838,2841,2842,2845,2846,2849],{},[579,2836,2837],{},"Debug",[19,2839,2840],{},"functools.wraps"," also sets ",[19,2843,2844],{},"__wrapped__"," on the wrapper, pointing back to the original function — use ",[19,2847,2848],{},"inspect.unwrap(decorated_func)"," to peel back an arbitrary number of decorator layers when debugging.",[2815,2851,2852,2855,2856,2859,2860,2862,2863,2865],{},[579,2853,2854],{},"Performance",": decorators that do expensive setup (opening connections, compiling regexes) should do that work once at decoration time (in the outer function), not on every call inside ",[19,2857,2858],{},"wrapper"," — the outer function's body runs exactly once, at ",[19,2861,1049],{}," application time, while ",[19,2864,2858],{},"'s body runs on every invocation.",[2815,2867,2868,1953,2870,2873],{},[579,2869,2819],{},[19,2871,2872],{},"contextlib.contextmanager"," (chapter 19) is implemented as a decorator over a generator function — recognizing the \"wrap a generator to change calling convention\" pattern here will make that chapter's internals click faster.",[2815,2875,2876,2878,2879,2882,2883,2886],{},[579,2877,2837],{},": if a decorated function's ",[19,2880,2881],{},"help()"," or docstring looks wrong, check for a missing ",[19,2884,2885],{},"@wraps(func)"," first — it is the single most common decorator bug and is easy to overlook because the code still runs correctly, just with broken introspection.",[14,2888,2890],{"id":2889},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[2812,2892,2893,2909,2932,2954,2965],{},[2815,2894,2895,2898,2899,2901,2902,2904,2905,2908],{},[579,2896,2897],{},"Closures over loop variables always bind late, to the variable's final value, not a per-iteration snapshot"," — this affects ",[19,2900,364],{},", nested ",[19,2903,69],{},", and any closure created inside a loop; fix with a default-argument snapshot (",[19,2906,2907],{},"lambda i=i: ...",") or a factory function.",[2815,2910,2911,2927,2928,2931],{},[579,2912,2913,2914,2916,2917,1734,2920,2923,2924],{},"A decorator without ",[19,2915,2584],{}," silently corrupts ",[19,2918,2919],{},"__name__",[19,2921,2922],{},"__doc__",", and ",[19,2925,2926],{},"__module__"," — code depending on introspection (documentation generators, some test frameworks' fixture discovery, ",[19,2929,2930],{},"pickle"," for module-level lookups) can break in ways that are hard to trace back to a missing one-line decorator.",[2815,2933,2934,2941,2942,2945,2946,2949,2950,2953],{},[579,2935,2936,2938,2939],{},[19,2937,572],{}," requires the variable to already exist in an enclosing function scope — it cannot create one, unlike ",[19,2940,799],{}," — ",[19,2943,2944],{},"nonlocal x"," where no enclosing function defines ",[19,2947,2948],{},"x"," raises ",[19,2951,2952],{},"SyntaxError: no binding for nonlocal 'x' found"," at compile time, not runtime.",[2815,2955,2956,2964],{},[579,2957,2958,2959,1836,2961,2963],{},"A parameterized decorator applied without calling it (",[19,2960,1721],{},[19,2962,1839],{},") passes the decorated function itself as the first configuration argument"," — this doesn't always fail loudly; if that argument happens to be used in a way that doesn't immediately error (e.g., stored but not called until later), the bug surfaces far from its cause.",[2815,2966,2967,2973,2974,2977,2978,2980],{},[579,2968,2969,2970,2972],{},"Class-based decorators (objects with ",[19,2971,2355],{},") replace the function with a non-function object"," — code that checks ",[19,2975,2976],{},"inspect.isfunction(obj)"," or relies on function-specific attributes without going through ",[19,2979,2844],{}," will behave differently than with a function-based decorator, even though both are callable.",[14,2982,2984],{"id":2983},"spot-the-bug","🧠 Spot the Bug",[575,2986,2987],{},"A UI framework registers click handlers for a row of buttons in a loop. All buttons report the same index when clicked. Find the bug.",[24,2989,2990],{"language":26},[28,2991,2993],{"className":30,"code":2992,"language":26,"meta":32,"style":32},"def build_handlers(n):\n    handlers = []\n    for index in range(n):\n        def handler():\n            print(f\"Button {index} clicked\")\n        handlers.append(handler)\n    return handlers\n\nbuttons = build_handlers(3)\nfor h in buttons:\n    h()\n",[19,2994,2995,3004,3013,3027,3036,3059,3064,3071,3075,3089,3101],{"__ignoreMap":32},[36,2996,2997,2999,3002],{"class":38,"line":39},[36,2998,69],{"class":68},[36,3000,3001],{"class":72}," build_handlers",[36,3003,1434],{"class":76},[36,3005,3006,3009,3011],{"class":38,"line":46},[36,3007,3008],{"class":76},"    handlers ",[36,3010,106],{"class":68},[36,3012,332],{"class":76},[36,3014,3015,3018,3021,3023,3025],{"class":38,"line":52},[36,3016,3017],{"class":68},"    for",[36,3019,3020],{"class":76}," index ",[36,3022,344],{"class":68},[36,3024,347],{"class":80},[36,3026,1434],{"class":76},[36,3028,3029,3031,3034],{"class":38,"line":58},[36,3030,1563],{"class":68},[36,3032,3033],{"class":72}," handler",[36,3035,601],{"class":76},[36,3037,3038,3040,3042,3044,3047,3049,3052,3054,3057],{"class":38,"line":65},[36,3039,1375],{"class":80},[36,3041,350],{"class":76},[36,3043,1380],{"class":68},[36,3045,3046],{"class":96},"\"Button ",[36,3048,1156],{"class":80},[36,3050,3051],{"class":76},"index",[36,3053,1162],{"class":80},[36,3055,3056],{"class":96}," clicked\"",[36,3058,203],{"class":76},[36,3060,3061],{"class":38,"line":93},[36,3062,3063],{"class":76},"        handlers.append(handler)\n",[36,3065,3066,3068],{"class":38,"line":100},[36,3067,178],{"class":68},[36,3069,3070],{"class":76}," handlers\n",[36,3072,3073],{"class":38,"line":115},[36,3074,62],{"emptyLinePlaceholder":61},[36,3076,3077,3080,3082,3085,3087],{"class":38,"line":120},[36,3078,3079],{"class":76},"buttons ",[36,3081,106],{"class":68},[36,3083,3084],{"class":76}," build_handlers(",[36,3086,353],{"class":80},[36,3088,203],{"class":76},[36,3090,3091,3093,3096,3098],{"class":38,"line":137},[36,3092,338],{"class":68},[36,3094,3095],{"class":76}," h ",[36,3097,344],{"class":68},[36,3099,3100],{"class":76}," buttons:\n",[36,3102,3103],{"class":38,"line":149},[36,3104,3105],{"class":76},"    h()\n",[3107,3108,3109,3113,3150,3153,3232],"details",{},[3110,3111,3112],"summary",{},"Answer",[575,3114,3115,3116,3119,3120,1734,3123,1734,3126,3129,3130,3133,3134,3137,3138,3140,3141,3143,3144,3146,3147,3149],{},"Prints ",[19,3117,3118],{},"Button 2 clicked"," three times instead of ",[19,3121,3122],{},"Button 0",[19,3124,3125],{},"Button 1",[19,3127,3128],{},"Button 2",". Each ",[19,3131,3132],{},"handler"," closure captures the ",[883,3135,3136],{},"variable"," ",[19,3139,3051],{},", not its value at the time ",[19,3142,3132],{}," was defined. By the time any handler is actually called (after the loop has finished), ",[19,3145,3051],{}," holds its final value from the last iteration, ",[19,3148,893],{}," — and since all three closures share the same enclosing scope, they all see that same final value.",[575,3151,3152],{},"The fix is to force each closure to capture the current value at definition time, via a default argument:",[24,3154,3155],{"language":26},[28,3156,3158],{"className":30,"code":3157,"language":26,"meta":32,"style":32},"def build_handlers(n):\n    handlers = []\n    for index in range(n):\n        def handler(index=index):\n            print(f\"Button {index} clicked\")\n        handlers.append(handler)\n    return handlers\n",[19,3159,3160,3168,3176,3188,3202,3222,3226],{"__ignoreMap":32},[36,3161,3162,3164,3166],{"class":38,"line":39},[36,3163,69],{"class":68},[36,3165,3001],{"class":72},[36,3167,1434],{"class":76},[36,3169,3170,3172,3174],{"class":38,"line":46},[36,3171,3008],{"class":76},[36,3173,106],{"class":68},[36,3175,332],{"class":76},[36,3177,3178,3180,3182,3184,3186],{"class":38,"line":52},[36,3179,3017],{"class":68},[36,3181,3020],{"class":76},[36,3183,344],{"class":68},[36,3185,347],{"class":80},[36,3187,1434],{"class":76},[36,3189,3190,3192,3194,3197,3199],{"class":38,"line":58},[36,3191,1563],{"class":68},[36,3193,3033],{"class":72},[36,3195,3196],{"class":76},"(index",[36,3198,106],{"class":68},[36,3200,3201],{"class":76},"index):\n",[36,3203,3204,3206,3208,3210,3212,3214,3216,3218,3220],{"class":38,"line":65},[36,3205,1375],{"class":80},[36,3207,350],{"class":76},[36,3209,1380],{"class":68},[36,3211,3046],{"class":96},[36,3213,1156],{"class":80},[36,3215,3051],{"class":76},[36,3217,1162],{"class":80},[36,3219,3056],{"class":96},[36,3221,203],{"class":76},[36,3223,3224],{"class":38,"line":93},[36,3225,3063],{"class":76},[36,3227,3228,3230],{"class":38,"line":100},[36,3229,178],{"class":68},[36,3231,3070],{"class":76},[575,3233,3234,3237],{},[579,3235,3236],{},"The lesson",": closures capture variables by reference to the enclosing scope's cell, not snapshots of values — anything created in a loop and called later needs an explicit per-iteration binding (default argument or factory function) to avoid all instances sharing the loop's final state.",[14,3239,3241],{"id":3240},"key-takeaways","Key Takeaways",[2812,3243,3244,3247,3253,3265,3274,3281],{},[2815,3245,3246],{},"A closure captures enclosing-scope variables by reference, keeping them alive after the outer function returns — this is how Python builds stateful callables without a class.",[2815,3248,3249,3250,3252],{},"Closures over loop variables bind late (to the final value), not per-iteration — snapshot with a default argument (",[19,3251,2907],{},") or a factory function to avoid the classic loop-closure bug.",[2815,3254,3255,3257,3258,3261,3262,2690],{},[19,3256,572],{}," lets an inner function ",[883,3259,3260],{},"reassign"," an enclosing function's variable; without it, assignment creates a new local variable and mutating attempts raise ",[19,3263,3264],{},"UnboundLocalError",[2815,3266,3267,3268,3270,3271,2690],{},"A decorator is just a function that takes a callable and returns a (usually wrapping) callable — ",[19,3269,1049],{}," is sugar for ",[19,3272,3273],{},"func = decorator(func)",[2815,3275,3276,3277,3280],{},"Parameterized decorators need three nested function levels; always apply ",[19,3278,3279],{},"@functools.wraps(func)"," inside the innermost wrapper to preserve the original function's metadata.",[2815,3282,3283,3284,3287,3288,2690],{},"Stacked decorators apply bottom-up (closest to the function first) — ",[19,3285,3286],{},"@a @b def f"," behaves as ",[19,3289,3290],{},"a(b(f))",[3292,3293,3294],"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 .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}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 .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);}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}",{"title":32,"searchDepth":46,"depth":46,"links":3296},[3297,3299,3301,3302,3305,3308,3309,3310,3311,3312,3313,3314],{"id":16,"depth":46,"text":3298},"Closure Mechanics — __closure__ and Cell Objects",{"id":569,"depth":46,"text":3300},"nonlocal — Mutating an Enclosing Variable",{"id":806,"depth":46,"text":807},{"id":1042,"depth":46,"text":1043,"children":3303},[3304],{"id":1246,"depth":52,"text":1247},{"id":1485,"depth":46,"text":1486,"children":3306},[3307],{"id":1850,"depth":52,"text":1851},{"id":2151,"depth":46,"text":2152},{"id":2348,"depth":46,"text":2349},{"id":2588,"depth":46,"text":2589},{"id":2809,"depth":46,"text":2810},{"id":2889,"depth":46,"text":2890},{"id":2983,"depth":46,"text":2984},{"id":3240,"depth":46,"text":3241},"md",{},"\u002Fpython\u002F11-closures-and-decorators",{"title":5,"description":32},"python\u002F11-closures-and-decorators","6oIDs7eOjLRl8Fr_qWx_9vkg6M2lMgtS6IvLasZPUro",1789924651496]