[{"data":1,"prerenderedAt":2826},["ShallowReactive",2],{"page-\u002Fpython\u002F11-closures-and-decorators":3},{"id":4,"title":5,"body":6,"description":323,"extension":2820,"meta":2821,"navigation":34,"path":2822,"seo":2823,"stem":2824,"__hash__":2825},"content\u002Fpython\u002F11-closures-and-decorators.md","11 — Closures & Decorators",{"type":7,"value":8,"toc":2789},"minimark",[9,13,84,144,219,308,450,522,579,737,829,989,1212,1468,1674,1939,2177,2252,2318,2383,2645,2785],[10,11,5],"h1",{"id":12},"_11-closures-decorators",[14,15,16,21,59],"question-wrapper",{},[17,18,20],"h3",{"id":19},"q1-what-is-a-closure-in-python","Q1. What is a closure in Python?",[22,23,26,37,43,49],"ul",{"className":24},[25],"contains-task-list",[27,28,31,36],"li",{"className":29},[30],"task-list-item",[32,33],"input",{"disabled":34,"type":35},true,"checkbox"," A function that has access to variables from its enclosing lexical scope, even after the enclosing function has finished executing",[27,38,40,42],{"className":39},[30],[32,41],{"disabled":34,"type":35}," Any function defined inside a class body",[27,44,46,48],{"className":45},[30],[32,47],{"disabled":34,"type":35}," A function that automatically closes open file handles when it returns",[27,50,52,54,55],{"className":51},[30],[32,53],{"disabled":34,"type":35}," A function decorated with ",[56,57,58],"code",{},"@staticmethod",[60,61,62,66,74],"details",{},[63,64,65],"summary",{},"Show Answer",[67,68,69,73],"p",{},[70,71,72],"strong",{},"Answer:"," A — A function that has access to variables from its enclosing lexical scope, even after the enclosing function has finished executing",[67,75,76,79,80,83],{},[70,77,78],{},"Explanation:"," A closure is an inner function that \"remembers\" the variables from its enclosing scope via a cell object, so it can still read (and with ",[56,81,82],{},"nonlocal",", write) them long after the outer function has returned. It has nothing to do with classes or file handles — those are unrelated meanings of \"closing\" that beginners sometimes conflate with the term.",[14,85,86,93,120],{},[17,87,89,90,92],{"id":88},"q2-what-does-the-nonlocal-keyword-do","Q2. What does the ",[56,91,82],{}," keyword do?",[22,94,96,102,108,114],{"className":95},[25],[27,97,99,101],{"className":98},[30],[32,100],{"disabled":34,"type":35}," Lets an inner function assign to a variable defined in its nearest enclosing (non-global) function scope",[27,103,105,107],{"className":104},[30],[32,106],{"disabled":34,"type":35}," Lets an inner function assign to a variable in the module's global scope",[27,109,111,113],{"className":110},[30],[32,112],{"disabled":34,"type":35}," Declares a variable that is shared across all instances of a class",[27,115,117,119],{"className":116},[30],[32,118],{"disabled":34,"type":35}," Makes a variable visible to every thread without a lock",[60,121,122,124,129],{},[63,123,65],{},[67,125,126,128],{},[70,127,72],{}," A — Lets an inner function assign to a variable defined in its nearest enclosing (non-global) function scope",[67,130,131,133,134,136,137,139,140,143],{},[70,132,78],{}," Without ",[56,135,82],{},", assigning to a name inside a nested function creates a brand-new local variable that shadows the outer one instead of modifying it. ",[56,138,82],{}," tells Python to bind to the enclosing function's variable instead. ",[56,141,142],{},"global"," (option B) is the different keyword for module-level scope — mixing the two up is a common mistake.",[14,145,146,158,194],{},[17,147,149,150,153,154,157],{"id":148},"q3-my_decorator-placed-above-def-func-is-syntactic-sugar-for-which-statement","Q3. ",[56,151,152],{},"@my_decorator"," placed above ",[56,155,156],{},"def func(): ..."," is syntactic sugar for which statement?",[22,159,161,170,178,186],{"className":160},[25],[27,162,164,166,167],{"className":163},[30],[32,165],{"disabled":34,"type":35}," ",[56,168,169],{},"func = my_decorator(func)",[27,171,173,166,175],{"className":172},[30],[32,174],{"disabled":34,"type":35},[56,176,177],{},"func = my_decorator()(func)",[27,179,181,166,183],{"className":180},[30],[32,182],{"disabled":34,"type":35},[56,184,185],{},"my_decorator.func = func",[27,187,189,166,191],{"className":188},[30],[32,190],{"disabled":34,"type":35},[56,192,193],{},"func = my_decorator",[60,195,196,198,205],{},[63,197,65],{},[67,199,200,202,203],{},[70,201,72],{}," A — ",[56,204,169],{},[67,206,207,209,210,214,215,218],{},[70,208,78],{}," A plain decorator is just a function that takes the decorated function as its single argument and returns a (usually different) callable, which is rebound to the original name. Option B is the expansion for a decorator ",[211,212,213],"em",{},"factory"," called with arguments, like ",[56,216,217],{},"@my_decorator(arg)",", not a plain decorator.",[14,220,221,229,275],{},[17,222,224,225,228],{"id":223},"q4-what-does-functoolswraps-do-when-applied-inside-a-decorators-wrapper-function","Q4. What does ",[56,226,227],{},"functools.wraps"," do when applied inside a decorator's wrapper function?",[22,230,232,246,260,269],{"className":231},[25],[27,233,235,237,238,241,242,245],{"className":234},[30],[32,236],{"disabled":34,"type":35}," Copies ",[56,239,240],{},"__name__",", ",[56,243,244],{},"__doc__",", and other metadata from the original function onto the wrapper",[27,247,249,251,252,255,256,259],{"className":248},[30],[32,250],{"disabled":34,"type":35}," Wraps the return value of the function in a ",[56,253,254],{},"try","\u002F",[56,257,258],{},"except"," block automatically",[27,261,263,265,266],{"className":262},[30],[32,264],{"disabled":34,"type":35}," Caches the function's return value like ",[56,267,268],{},"lru_cache",[27,270,272,274],{"className":271},[30],[32,273],{"disabled":34,"type":35}," Converts a regular function into a coroutine",[60,276,277,279,288],{},[63,278,65],{},[67,280,281,283,284,241,286,245],{},[70,282,72],{}," A — Copies ",[56,285,240],{},[56,287,244],{},[67,289,290,133,292,295,296,299,300,303,304,307],{},[70,291,78],{},[56,293,294],{},"@functools.wraps(func)"," on the inner ",[56,297,298],{},"wrapper",", introspection tools, debuggers, and ",[56,301,302],{},"help()"," will report the wrapper's own name (typically ",[56,305,306],{},"\"wrapper\"",") and lose the original docstring, which makes stack traces and documentation confusing. It does not add error handling or caching — those are unrelated decorator patterns.",[14,309,311,315,362,373,413],{"language":310},"python",[17,312,314],{"id":313},"q5-given-stacked-decorators","Q5. Given stacked decorators:",[316,317,318],"code-wrapper",{"language":310},[319,320,324],"pre",{"className":321,"code":322,"language":310,"meta":323,"style":323},"language-python shiki shiki-themes github-light github-dark","@first\n@second\ndef handler():\n    ...\n","",[56,325,326,335,341,355],{"__ignoreMap":323},[327,328,331],"span",{"class":329,"line":330},"line",1,[327,332,334],{"class":333},"sIsaT","@first\n",[327,336,338],{"class":329,"line":337},2,[327,339,340],{"class":333},"@second\n",[327,342,344,348,351],{"class":329,"line":343},3,[327,345,347],{"class":346},"svdQ7","def",[327,349,350],{"class":333}," handler",[327,352,354],{"class":353},"ssxIu","():\n",[327,356,358],{"class":329,"line":357},4,[327,359,361],{"class":360},"snvgF","    ...\n",[67,363,364,365,368,369,372],{},"Which decorator's wrapping logic executes ",[211,366,367],{},"first"," when ",[56,370,371],{},"handler()"," is called?",[22,374,376,387,395,401],{"className":375},[25],[27,377,379,166,381,384,385],{"className":378},[30],[32,380],{"disabled":34,"type":35},[56,382,383],{},"second","'s, because it wraps the original function directly and runs on the way in before control reaches ",[56,386,367],{},[27,388,390,166,392,394],{"className":389},[30],[32,391],{"disabled":34,"type":35},[56,393,367],{},"'s, because it's listed first in the source",[27,396,398,400],{"className":397},[30],[32,399],{"disabled":34,"type":35}," Both run simultaneously",[27,402,404,406,407,409,410,412],{"className":403},[30],[32,405],{"disabled":34,"type":35}," Neither — only ",[56,408,367],{}," applies; ",[56,411,383],{}," is silently discarded",[60,414,415,417,425],{},[63,416,65],{},[67,418,419,202,421,384,423],{},[70,420,72],{},[56,422,383],{},[56,424,367],{},[67,426,427,429,430,433,434,436,437,439,440,442,443,445,446,449],{},[70,428,78],{}," Decorators apply bottom-up but execute outside-in: ",[56,431,432],{},"handler = first(second(handler))",", so ",[56,435,367],{},"'s wrapper is the outermost call, but its body typically calls the thing it wraps (",[56,438,383],{},"'s wrapper) before that call returns — so on entry, ",[56,441,367],{},"'s pre-call code runs, then ",[56,444,383],{},"'s pre-call code, then the real function. The naive assumption that \"top decorator runs first in every sense\" is the beginner trap; it's true for ",[211,447,448],{},"application order",", not necessarily for every line of runtime behavior.",[14,451,452,460,487],{},[17,453,455,456,459],{"id":454},"q6-a-decorator-that-itself-accepts-arguments-eg-retrytimes3-requires-how-many-levels-of-nested-functions","Q6. A decorator that itself accepts arguments, e.g. ",[56,457,458],{},"@retry(times=3)",", requires how many levels of nested functions?",[22,461,463,469,475,481],{"className":462},[25],[27,464,466,468],{"className":465},[30],[32,467],{"disabled":34,"type":35}," Three — an outer factory taking the decorator's arguments, a middle decorator taking the function, and an inner wrapper taking the function's call arguments",[27,470,472,474],{"className":471},[30],[32,473],{"disabled":34,"type":35}," One — the decorator function itself takes both the decorator arguments and the function",[27,476,478,480],{"className":477},[30],[32,479],{"disabled":34,"type":35}," Two — a decorator taking the function, and a wrapper taking call arguments",[27,482,484,486],{"className":483},[30],[32,485],{"disabled":34,"type":35}," Four — Python requires a separate factory for each keyword argument",[60,488,489,491,496],{},[63,490,65],{},[67,492,493,495],{},[70,494,72],{}," A — Three — an outer factory taking the decorator's arguments, a middle decorator taking the function, and an inner wrapper taking the function's call arguments",[67,497,498,166,500,503,504,507,508,511,512,514,515,166,518,521],{},[70,499,78],{},[56,501,502],{},"retry(times=3)"," must first return an actual decorator (level 2), which is then called with the function (",[56,505,506],{},"func","), which must return a ",[56,509,510],{},"wrapper(*args, **kwargs)"," (level 3) that does the real work. Beginners often try to collapse this into two levels (option C), which works for plain decorators but not parameterized ones, since ",[56,513,458],{}," calls ",[56,516,517],{},"retry",[211,519,520],{},"before"," decoration even begins.",[14,523,524,528,559],{},[17,525,527],{"id":526},"q7-what-does-a-python-closure-actually-capture-from-the-enclosing-scope","Q7. What does a Python closure actually capture from the enclosing scope?",[22,529,531,541,547,553],{"className":530},[25],[27,532,534,536,537,540],{"className":533},[30],[32,535],{"disabled":34,"type":35}," A reference to the variable's cell, so the closure always sees the variable's ",[211,538,539],{},"current"," value, not a snapshot taken at definition time",[27,542,544,546],{"className":543},[30],[32,545],{"disabled":34,"type":35}," A deep copy of the variable's value at the moment the inner function is defined",[27,548,550,552],{"className":549},[30],[32,551],{"disabled":34,"type":35}," A shallow copy of the value, copied only if the value is mutable",[27,554,556,558],{"className":555},[30],[32,557],{"disabled":34,"type":35}," Nothing — closures re-evaluate the enclosing function's source code on each call",[60,560,561,563,570],{},[63,562,65],{},[67,564,565,567,568,540],{},[70,566,72],{}," A — A reference to the variable's cell, so the closure always sees the variable's ",[211,569,539],{},[67,571,572,574,575,578],{},[70,573,78],{}," Python closures bind by reference to a shared cell object, not by value. This is exactly why the late-binding loop-variable gotcha exists: every closure created in a loop shares the ",[211,576,577],{},"same"," cell for the loop variable, so they all observe whatever that variable holds when they're eventually called, not when they were created.",[14,580,581,585,661,696],{"language":310},[17,582,584],{"id":583},"q8-what-does-this-print","Q8. What does this print?",[316,586,587],{"language":310},[319,588,590],{"className":321,"code":589,"language":310,"meta":323,"style":323},"callbacks = []\nfor i in range(3):\n    callbacks.append(lambda: i)\n\nprint([cb() for cb in callbacks])\n",[56,591,592,603,626,637,642],{"__ignoreMap":323},[327,593,594,597,600],{"class":329,"line":330},[327,595,596],{"class":353},"callbacks ",[327,598,599],{"class":346},"=",[327,601,602],{"class":353}," []\n",[327,604,605,608,611,614,617,620,623],{"class":329,"line":337},[327,606,607],{"class":346},"for",[327,609,610],{"class":353}," i ",[327,612,613],{"class":346},"in",[327,615,616],{"class":360}," range",[327,618,619],{"class":353},"(",[327,621,622],{"class":360},"3",[327,624,625],{"class":353},"):\n",[327,627,628,631,634],{"class":329,"line":343},[327,629,630],{"class":353},"    callbacks.append(",[327,632,633],{"class":346},"lambda",[327,635,636],{"class":353},": i)\n",[327,638,639],{"class":329,"line":357},[327,640,641],{"emptyLinePlaceholder":34},"\n",[327,643,645,648,651,653,656,658],{"class":329,"line":644},5,[327,646,647],{"class":360},"print",[327,649,650],{"class":353},"([cb() ",[327,652,607],{"class":346},[327,654,655],{"class":353}," cb ",[327,657,613],{"class":346},[327,659,660],{"class":353}," callbacks])\n",[22,662,664,672,680,688],{"className":663},[25],[27,665,667,166,669],{"className":666},[30],[32,668],{"disabled":34,"type":35},[56,670,671],{},"[2, 2, 2]",[27,673,675,166,677],{"className":674},[30],[32,676],{"disabled":34,"type":35},[56,678,679],{},"[0, 1, 2]",[27,681,683,166,685],{"className":682},[30],[32,684],{"disabled":34,"type":35},[56,686,687],{},"[0, 0, 0]",[27,689,691,166,693],{"className":690},[30],[32,692],{"disabled":34,"type":35},[56,694,695],{},"RuntimeError: variable modified during iteration",[60,697,698,700,706],{},[63,699,65],{},[67,701,702,202,704],{},[70,703,72],{},[56,705,671],{},[67,707,708,166,710,713,714,716,717,720,721,723,724,726,727,730,731,733,734,736],{},[70,709,78],{},[70,711,712],{},"Debug"," — All three lambdas close over the ",[211,715,577],{}," variable ",[56,718,719],{},"i",", not three independent copies. By the time the list comprehension calls them, the ",[56,722,607],{}," loop has already finished and ",[56,725,719],{}," holds its final value, ",[56,728,729],{},"2",". The tempting ",[56,732,679],{}," answer assumes each lambda captures the value of ",[56,735,719],{}," at the point it was created, which is how closures work in some other languages but not in Python's late-binding model.",[14,738,739,746,796],{},[17,740,742,743,745],{"id":741},"q9-which-change-correctly-fixes-the-late-binding-bug-from-q8-so-the-output-is-0-1-2","Q9. Which change correctly fixes the late-binding bug from Q8 so the output is ",[56,744,679],{},"?",[22,747,749,761,774,787],{"className":748},[25],[27,750,752,166,754,757,758,760],{"className":751},[30],[32,753],{"disabled":34,"type":35},[56,755,756],{},"callbacks.append(lambda i=i: i)"," — bind ",[56,759,719],{},"'s current value as a default argument at lambda-creation time",[27,762,764,166,766,769,770,773],{"className":763},[30],[32,765],{"disabled":34,"type":35},[56,767,768],{},"callbacks.append(lambda: int(i))"," — wrapping in ",[56,771,772],{},"int()"," forces early evaluation",[27,775,777,779,780,782,783,786],{"className":776},[30],[32,778],{"disabled":34,"type":35}," Replace the ",[56,781,607],{}," loop with a ",[56,784,785],{},"while"," loop",[27,788,790,166,792,795],{"className":789},[30],[32,791],{"disabled":34,"type":35},[56,793,794],{},"callbacks.append(lambda: i.copy())"," — copy the integer before storing it",[60,797,798,800,808],{},[63,799,65],{},[67,801,802,202,804,757,806,760],{},[70,803,72],{},[56,805,756],{},[56,807,719],{},[67,809,810,812,813,816,817,820,821,824,825,828],{},[70,811,78],{}," Default argument values ",[211,814,815],{},"are"," evaluated once, at function-definition time, so ",[56,818,819],{},"i=i"," captures the loop variable's value on each iteration into a fresh, per-lambda default. ",[56,822,823],{},"int(i)"," (option B) still reads the shared cell at call time, so it doesn't help; integers have no ",[56,826,827],{},".copy()"," method (option D), and switching loop constructs (option C) doesn't change how closures bind names.",[14,830,831,835,895,949],{"language":310},[17,832,834],{"id":833},"q10-what-happens-when-this-code-runs","Q10. What happens when this code runs?",[316,836,837],{"language":310},[319,838,840],{"className":321,"code":839,"language":310,"meta":323,"style":323},"def outer():\n    def inner():\n        nonlocal missing\n        missing = 1\n    inner()\n\nouter()\n",[56,841,842,851,861,869,879,884,889],{"__ignoreMap":323},[327,843,844,846,849],{"class":329,"line":330},[327,845,347],{"class":346},[327,847,848],{"class":333}," outer",[327,850,354],{"class":353},[327,852,853,856,859],{"class":329,"line":337},[327,854,855],{"class":346},"    def",[327,857,858],{"class":333}," inner",[327,860,354],{"class":353},[327,862,863,866],{"class":329,"line":343},[327,864,865],{"class":346},"        nonlocal",[327,867,868],{"class":353}," missing\n",[327,870,871,874,876],{"class":329,"line":357},[327,872,873],{"class":353},"        missing ",[327,875,599],{"class":346},[327,877,878],{"class":360}," 1\n",[327,880,881],{"class":329,"line":644},[327,882,883],{"class":353},"    inner()\n",[327,885,887],{"class":329,"line":886},6,[327,888,641],{"emptyLinePlaceholder":34},[327,890,892],{"class":329,"line":891},7,[327,893,894],{"class":353},"outer()\n",[22,896,898,911,924,934],{"className":897},[25],[27,899,901,166,903,906,907,910],{"className":900},[30],[32,902],{"disabled":34,"type":35},[56,904,905],{},"SyntaxError: no binding for nonlocal 'missing' found"," — raised at compile time, before ",[56,908,909],{},"outer()"," is ever called",[27,912,914,166,916,919,920,923],{"className":913},[30],[32,915],{"disabled":34,"type":35},[56,917,918],{},"UnboundLocalError"," raised when ",[56,921,922],{},"inner()"," executes",[27,925,927,929,930,933],{"className":926},[30],[32,928],{"disabled":34,"type":35}," It runs fine; ",[56,931,932],{},"missing"," becomes a new global variable",[27,935,937,929,939,941,942,944,945,948],{"className":936},[30],[32,938],{"disabled":34,"type":35},[56,940,82],{}," silently creates ",[56,943,932],{}," in ",[56,946,947],{},"outer","'s scope",[60,950,951,953,961],{},[63,952,65],{},[67,954,955,202,957,906,959,910],{},[70,956,72],{},[56,958,905],{},[56,960,909],{},[67,962,963,965,966,968,969,971,972,975,976,978,979,981,982,985,986,988],{},[70,964,78],{}," Unlike ",[56,967,142],{},", which will happily create a new module-level name if one doesn't exist, ",[56,970,82],{}," requires that some enclosing function scope ",[211,973,974],{},"already"," bind that name (e.g., via assignment) — Python checks this at compile time. Since ",[56,977,947],{}," never assigns ",[56,980,932],{}," anywhere, the whole module fails to compile with a ",[56,983,984],{},"SyntaxError",", not a runtime error, which surprises people who expect the failure to happen only when ",[56,987,922],{}," is called.",[14,990,991,995,1131,1169],{"language":310},[17,992,994],{"id":993},"q11-a-shared-mutable-counter-is-implemented-two-ways-which-one-actually-works-in-python-3-without-extra-tricks","Q11. A shared mutable counter is implemented two ways. Which one actually works in Python 3 without extra tricks?",[316,996,997],{"language":310},[319,998,1000],{"className":321,"code":999,"language":310,"meta":323,"style":323},"# Version A\ndef make_counter_a():\n    count = 0\n    def increment():\n        nonlocal count\n        count += 1\n        return count\n    return increment\n\n# Version B\ndef make_counter_b():\n    count = 0\n    def increment():\n        count += 1\n        return count\n    return increment\n",[56,1001,1002,1008,1017,1027,1036,1043,1053,1060,1069,1074,1080,1090,1099,1108,1117,1124],{"__ignoreMap":323},[327,1003,1004],{"class":329,"line":330},[327,1005,1007],{"class":1006},"sdCPZ","# Version A\n",[327,1009,1010,1012,1015],{"class":329,"line":337},[327,1011,347],{"class":346},[327,1013,1014],{"class":333}," make_counter_a",[327,1016,354],{"class":353},[327,1018,1019,1022,1024],{"class":329,"line":343},[327,1020,1021],{"class":353},"    count ",[327,1023,599],{"class":346},[327,1025,1026],{"class":360}," 0\n",[327,1028,1029,1031,1034],{"class":329,"line":357},[327,1030,855],{"class":346},[327,1032,1033],{"class":333}," increment",[327,1035,354],{"class":353},[327,1037,1038,1040],{"class":329,"line":644},[327,1039,865],{"class":346},[327,1041,1042],{"class":353}," count\n",[327,1044,1045,1048,1051],{"class":329,"line":886},[327,1046,1047],{"class":353},"        count ",[327,1049,1050],{"class":346},"+=",[327,1052,878],{"class":360},[327,1054,1055,1058],{"class":329,"line":891},[327,1056,1057],{"class":346},"        return",[327,1059,1042],{"class":353},[327,1061,1063,1066],{"class":329,"line":1062},8,[327,1064,1065],{"class":346},"    return",[327,1067,1068],{"class":353}," increment\n",[327,1070,1072],{"class":329,"line":1071},9,[327,1073,641],{"emptyLinePlaceholder":34},[327,1075,1077],{"class":329,"line":1076},10,[327,1078,1079],{"class":1006},"# Version B\n",[327,1081,1083,1085,1088],{"class":329,"line":1082},11,[327,1084,347],{"class":346},[327,1086,1087],{"class":333}," make_counter_b",[327,1089,354],{"class":353},[327,1091,1093,1095,1097],{"class":329,"line":1092},12,[327,1094,1021],{"class":353},[327,1096,599],{"class":346},[327,1098,1026],{"class":360},[327,1100,1102,1104,1106],{"class":329,"line":1101},13,[327,1103,855],{"class":346},[327,1105,1033],{"class":333},[327,1107,354],{"class":353},[327,1109,1111,1113,1115],{"class":329,"line":1110},14,[327,1112,1047],{"class":353},[327,1114,1050],{"class":346},[327,1116,878],{"class":360},[327,1118,1120,1122],{"class":329,"line":1119},15,[327,1121,1057],{"class":346},[327,1123,1042],{"class":353},[327,1125,1127,1129],{"class":329,"line":1126},16,[327,1128,1065],{"class":346},[327,1130,1068],{"class":353},[22,1132,1134,1146,1154,1160],{"className":1133},[25],[27,1135,1137,1139,1140,1142,1143],{"className":1136},[30],[32,1138],{"disabled":34,"type":35}," Only Version A works; Version B raises ",[56,1141,918],{}," on the first call to ",[56,1144,1145],{},"increment()",[27,1147,1149,1151,1152],{"className":1148},[30],[32,1150],{"disabled":34,"type":35}," Only Version B works; Version A raises a ",[56,1153,984],{},[27,1155,1157,1159],{"className":1156},[30],[32,1158],{"disabled":34,"type":35}," Both versions work identically",[27,1161,1163,1165,1166,1168],{"className":1162},[30],[32,1164],{"disabled":34,"type":35}," Neither works; both need a ",[56,1167,142],{}," declaration",[60,1170,1171,1173,1182],{},[63,1172,65],{},[67,1174,1175,1177,1178,1142,1180],{},[70,1176,72],{}," A — Only Version A works; Version B raises ",[56,1179,918],{},[56,1181,1145],{},[67,1183,1184,166,1186,1189,1190,1193,1194,1197,1198,1201,1202,1204,1205,1207,1208,1211],{},[70,1185,78],{},[56,1187,1188],{},"count += 1"," is equivalent to ",[56,1191,1192],{},"count = count + 1",", and the presence of that assignment makes Python treat ",[56,1195,1196],{},"count"," as local to ",[56,1199,1200],{},"increment"," at compile time — so the read on the right-hand side happens before any local ",[56,1203,1196],{}," has been assigned, raising ",[56,1206,918],{},". Version A's ",[56,1209,1210],{},"nonlocal count"," tells Python to use the enclosing cell instead of creating a new local, which is exactly the fix this pattern needs.",[14,1213,1214,1226,1395,1437],{"language":310},[17,1215,1217,1218,1221,1222,1225],{"id":1216},"q12-a-decorator-wraps-an-instance-method-but-the-wrapper-is-defined-as-def-wrapperargs-no-kwargs-what-breaks","Q12. A decorator wraps an instance method but the wrapper is defined as ",[56,1219,1220],{},"def wrapper(*args): ..."," (no ",[56,1223,1224],{},"**kwargs","). What breaks?",[316,1227,1228],{"language":310},[319,1229,1231],{"className":321,"code":1230,"language":310,"meta":323,"style":323},"def log_call(func):\n    def wrapper(*args):\n        print(f\"Calling {func.__name__}\")\n        return func(*args)\n    return wrapper\n\nclass Service:\n    @log_call\n    def fetch(self, url, timeout=5):\n        return f\"{url} in {timeout}s\"\n\nService().fetch(\"\u002Fapi\", timeout=2)\n",[56,1232,1233,1243,1258,1287,1299,1306,1310,1321,1326,1343,1372,1376],{"__ignoreMap":323},[327,1234,1235,1237,1240],{"class":329,"line":330},[327,1236,347],{"class":346},[327,1238,1239],{"class":333}," log_call",[327,1241,1242],{"class":353},"(func):\n",[327,1244,1245,1247,1250,1252,1255],{"class":329,"line":337},[327,1246,855],{"class":346},[327,1248,1249],{"class":333}," wrapper",[327,1251,619],{"class":353},[327,1253,1254],{"class":346},"*",[327,1256,1257],{"class":353},"args):\n",[327,1259,1260,1263,1265,1268,1272,1275,1278,1281,1284],{"class":329,"line":343},[327,1261,1262],{"class":360},"        print",[327,1264,619],{"class":353},[327,1266,1267],{"class":346},"f",[327,1269,1271],{"class":1270},"sJ6F3","\"Calling ",[327,1273,1274],{"class":360},"{",[327,1276,1277],{"class":353},"func.",[327,1279,1280],{"class":360},"__name__}",[327,1282,1283],{"class":1270},"\"",[327,1285,1286],{"class":353},")\n",[327,1288,1289,1291,1294,1296],{"class":329,"line":357},[327,1290,1057],{"class":346},[327,1292,1293],{"class":353}," func(",[327,1295,1254],{"class":346},[327,1297,1298],{"class":353},"args)\n",[327,1300,1301,1303],{"class":329,"line":644},[327,1302,1065],{"class":346},[327,1304,1305],{"class":353}," wrapper\n",[327,1307,1308],{"class":329,"line":886},[327,1309,641],{"emptyLinePlaceholder":34},[327,1311,1312,1315,1318],{"class":329,"line":891},[327,1313,1314],{"class":346},"class",[327,1316,1317],{"class":333}," Service",[327,1319,1320],{"class":353},":\n",[327,1322,1323],{"class":329,"line":1062},[327,1324,1325],{"class":333},"    @log_call\n",[327,1327,1328,1330,1333,1336,1338,1341],{"class":329,"line":1071},[327,1329,855],{"class":346},[327,1331,1332],{"class":333}," fetch",[327,1334,1335],{"class":353},"(self, url, timeout",[327,1337,599],{"class":346},[327,1339,1340],{"class":360},"5",[327,1342,625],{"class":353},[327,1344,1345,1347,1350,1352,1354,1357,1360,1362,1364,1367,1369],{"class":329,"line":1076},[327,1346,1057],{"class":346},[327,1348,1349],{"class":346}," f",[327,1351,1283],{"class":1270},[327,1353,1274],{"class":360},[327,1355,1356],{"class":353},"url",[327,1358,1359],{"class":360},"}",[327,1361,944],{"class":1270},[327,1363,1274],{"class":360},[327,1365,1366],{"class":353},"timeout",[327,1368,1359],{"class":360},[327,1370,1371],{"class":1270},"s\"\n",[327,1373,1374],{"class":329,"line":1082},[327,1375,641],{"emptyLinePlaceholder":34},[327,1377,1378,1381,1384,1386,1389,1391,1393],{"class":329,"line":1092},[327,1379,1380],{"class":353},"Service().fetch(",[327,1382,1383],{"class":1270},"\"\u002Fapi\"",[327,1385,241],{"class":353},[327,1387,1366],{"class":1388},"sCrzJ",[327,1390,599],{"class":346},[327,1392,729],{"class":360},[327,1394,1286],{"class":353},[22,1396,1398,1406,1416,1429],{"className":1397},[25],[27,1399,1401,166,1403],{"className":1400},[30],[32,1402],{"disabled":34,"type":35},[56,1404,1405],{},"TypeError: wrapper() got an unexpected keyword argument 'timeout'",[27,1407,1409,1411,1412,1415],{"className":1408},[30],[32,1410],{"disabled":34,"type":35}," It works fine; ",[56,1413,1414],{},"*args"," silently absorbs keyword arguments too",[27,1417,1419,166,1421,1424,1425,1428],{"className":1418},[30],[32,1420],{"disabled":34,"type":35},[56,1422,1423],{},"self"," is dropped and ",[56,1426,1427],{},"fetch"," is called as a static method",[27,1430,1432,166,1434],{"className":1431},[30],[32,1433],{"disabled":34,"type":35},[56,1435,1436],{},"AttributeError: 'Service' object has no attribute 'fetch'",[60,1438,1439,1441,1447],{},[63,1440,65],{},[67,1442,1443,202,1445],{},[70,1444,72],{},[56,1446,1405],{},[67,1448,1449,166,1451,1453,1454,1457,1458,1461,1462,1464,1465,1467],{},[70,1450,78],{},[56,1452,1414],{}," only collects positional arguments; a caller passing ",[56,1455,1456],{},"timeout=2"," as a keyword argument has nothing to bind to, so Python raises ",[56,1459,1460],{},"TypeError",". A decorator meant to be transparent for arbitrary wrapped callables must define ",[56,1463,510],{}," and forward both. Beginners assume ",[56,1466,1414],{}," is a catch-all for \"anything,\" which is the trap here.",[14,1469,1470,1474,1584,1618],{"language":310},[17,1471,1473],{"id":1472},"q13-what-does-this-print","Q13. What does this print?",[316,1475,1476],{"language":310},[319,1477,1479],{"className":321,"code":1478,"language":310,"meta":323,"style":323},"def broken_decorator(func):\n    def wrapper(*args, **kwargs):\n        result = func(*args, **kwargs)\n        # forgot to return wrapper\n\n@broken_decorator\ndef add(a, b):\n    return a + b\n\nprint(add(2, 3))\n",[56,1480,1481,1490,1509,1527,1532,1536,1541,1551,1564,1568],{"__ignoreMap":323},[327,1482,1483,1485,1488],{"class":329,"line":330},[327,1484,347],{"class":346},[327,1486,1487],{"class":333}," broken_decorator",[327,1489,1242],{"class":353},[327,1491,1492,1494,1496,1498,1500,1503,1506],{"class":329,"line":337},[327,1493,855],{"class":346},[327,1495,1249],{"class":333},[327,1497,619],{"class":353},[327,1499,1254],{"class":346},[327,1501,1502],{"class":353},"args, ",[327,1504,1505],{"class":346},"**",[327,1507,1508],{"class":353},"kwargs):\n",[327,1510,1511,1514,1516,1518,1520,1522,1524],{"class":329,"line":343},[327,1512,1513],{"class":353},"        result ",[327,1515,599],{"class":346},[327,1517,1293],{"class":353},[327,1519,1254],{"class":346},[327,1521,1502],{"class":353},[327,1523,1505],{"class":346},[327,1525,1526],{"class":353},"kwargs)\n",[327,1528,1529],{"class":329,"line":357},[327,1530,1531],{"class":1006},"        # forgot to return wrapper\n",[327,1533,1534],{"class":329,"line":644},[327,1535,641],{"emptyLinePlaceholder":34},[327,1537,1538],{"class":329,"line":886},[327,1539,1540],{"class":333},"@broken_decorator\n",[327,1542,1543,1545,1548],{"class":329,"line":891},[327,1544,347],{"class":346},[327,1546,1547],{"class":333}," add",[327,1549,1550],{"class":353},"(a, b):\n",[327,1552,1553,1555,1558,1561],{"class":329,"line":1062},[327,1554,1065],{"class":346},[327,1556,1557],{"class":353}," a ",[327,1559,1560],{"class":346},"+",[327,1562,1563],{"class":353}," b\n",[327,1565,1566],{"class":329,"line":1071},[327,1567,641],{"emptyLinePlaceholder":34},[327,1569,1570,1572,1575,1577,1579,1581],{"class":329,"line":1076},[327,1571,647],{"class":360},[327,1573,1574],{"class":353},"(add(",[327,1576,729],{"class":360},[327,1578,241],{"class":353},[327,1580,622],{"class":360},[327,1582,1583],{"class":353},"))\n",[22,1585,1587,1595,1602,1610],{"className":1586},[25],[27,1588,1590,166,1592],{"className":1589},[30],[32,1591],{"disabled":34,"type":35},[56,1593,1594],{},"TypeError: 'NoneType' object is not callable",[27,1596,1598,166,1600],{"className":1597},[30],[32,1599],{"disabled":34,"type":35},[56,1601,1340],{},[27,1603,1605,166,1607],{"className":1604},[30],[32,1606],{"disabled":34,"type":35},[56,1608,1609],{},"None",[27,1611,1613,166,1615],{"className":1612},[30],[32,1614],{"disabled":34,"type":35},[56,1616,1617],{},"NameError: name 'add' is not defined",[60,1619,1620,1622,1628],{},[63,1621,65],{},[67,1623,1624,202,1626],{},[70,1625,72],{},[56,1627,1594],{},[67,1629,1630,166,1632,1634,1635,1638,1639,1641,1642,1644,1645,1648,1649,1652,1653,1655,1656,1659,1660,1662,1663,1665,1666,1669,1670,1673],{},[70,1631,78],{},[70,1633,712],{}," — ",[56,1636,1637],{},"broken_decorator"," never returns ",[56,1640,298],{}," (or anything), so it implicitly returns ",[56,1643,1609],{},". Since ",[56,1646,1647],{},"add = broken_decorator(add)",", the name ",[56,1650,1651],{},"add"," is rebound to ",[56,1654,1609],{},", and calling ",[56,1657,1658],{},"add(2, 3)"," tries to call ",[56,1661,1609],{},", which raises ",[56,1664,1460],{},". The fix is to always ",[56,1667,1668],{},"return wrapper"," from the decorator — a missing ",[56,1671,1672],{},"return"," is one of the most common real-world decorator bugs, and it fails at the call site, far from the actual mistake.",[14,1675,1676,1680,1856,1891],{"language":310},[17,1677,1679],{"id":1678},"q14-what-is-printed-by-this-decorator-with-arguments-code","Q14. What is printed by this decorator-with-arguments code?",[316,1681,1682],{"language":310},[319,1683,1685],{"className":321,"code":1684,"language":310,"meta":323,"style":323},"import functools\n\ndef repeat(times):\n    def decorator(func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            results = []\n            for _ in range(times):\n                results.append(func(*args, **kwargs))\n            return results\n        return wrapper\n    return decorator\n\n@repeat(times=2)\ndef shout(word):\n    return word.upper()\n\nprint(shout(\"hi\"))\n",[56,1686,1687,1695,1699,1709,1718,1726,1743,1752,1766,1780,1788,1794,1801,1805,1821,1831,1838,1843],{"__ignoreMap":323},[327,1688,1689,1692],{"class":329,"line":330},[327,1690,1691],{"class":346},"import",[327,1693,1694],{"class":353}," functools\n",[327,1696,1697],{"class":329,"line":337},[327,1698,641],{"emptyLinePlaceholder":34},[327,1700,1701,1703,1706],{"class":329,"line":343},[327,1702,347],{"class":346},[327,1704,1705],{"class":333}," repeat",[327,1707,1708],{"class":353},"(times):\n",[327,1710,1711,1713,1716],{"class":329,"line":357},[327,1712,855],{"class":346},[327,1714,1715],{"class":333}," decorator",[327,1717,1242],{"class":353},[327,1719,1720,1723],{"class":329,"line":644},[327,1721,1722],{"class":333},"        @functools.wraps",[327,1724,1725],{"class":353},"(func)\n",[327,1727,1728,1731,1733,1735,1737,1739,1741],{"class":329,"line":886},[327,1729,1730],{"class":346},"        def",[327,1732,1249],{"class":333},[327,1734,619],{"class":353},[327,1736,1254],{"class":346},[327,1738,1502],{"class":353},[327,1740,1505],{"class":346},[327,1742,1508],{"class":353},[327,1744,1745,1748,1750],{"class":329,"line":891},[327,1746,1747],{"class":353},"            results ",[327,1749,599],{"class":346},[327,1751,602],{"class":353},[327,1753,1754,1757,1760,1762,1764],{"class":329,"line":1062},[327,1755,1756],{"class":346},"            for",[327,1758,1759],{"class":353}," _ ",[327,1761,613],{"class":346},[327,1763,616],{"class":360},[327,1765,1708],{"class":353},[327,1767,1768,1771,1773,1775,1777],{"class":329,"line":1071},[327,1769,1770],{"class":353},"                results.append(func(",[327,1772,1254],{"class":346},[327,1774,1502],{"class":353},[327,1776,1505],{"class":346},[327,1778,1779],{"class":353},"kwargs))\n",[327,1781,1782,1785],{"class":329,"line":1076},[327,1783,1784],{"class":346},"            return",[327,1786,1787],{"class":353}," results\n",[327,1789,1790,1792],{"class":329,"line":1082},[327,1791,1057],{"class":346},[327,1793,1305],{"class":353},[327,1795,1796,1798],{"class":329,"line":1092},[327,1797,1065],{"class":346},[327,1799,1800],{"class":353}," decorator\n",[327,1802,1803],{"class":329,"line":1101},[327,1804,641],{"emptyLinePlaceholder":34},[327,1806,1807,1810,1812,1815,1817,1819],{"class":329,"line":1110},[327,1808,1809],{"class":333},"@repeat",[327,1811,619],{"class":353},[327,1813,1814],{"class":1388},"times",[327,1816,599],{"class":346},[327,1818,729],{"class":360},[327,1820,1286],{"class":353},[327,1822,1823,1825,1828],{"class":329,"line":1119},[327,1824,347],{"class":346},[327,1826,1827],{"class":333}," shout",[327,1829,1830],{"class":353},"(word):\n",[327,1832,1833,1835],{"class":329,"line":1126},[327,1834,1065],{"class":346},[327,1836,1837],{"class":353}," word.upper()\n",[327,1839,1841],{"class":329,"line":1840},17,[327,1842,641],{"emptyLinePlaceholder":34},[327,1844,1846,1848,1851,1854],{"class":329,"line":1845},18,[327,1847,647],{"class":360},[327,1849,1850],{"class":353},"(shout(",[327,1852,1853],{"class":1270},"\"hi\"",[327,1855,1583],{"class":353},[22,1857,1859,1867,1875,1883],{"className":1858},[25],[27,1860,1862,166,1864],{"className":1861},[30],[32,1863],{"disabled":34,"type":35},[56,1865,1866],{},"['HI', 'HI']",[27,1868,1870,166,1872],{"className":1869},[30],[32,1871],{"disabled":34,"type":35},[56,1873,1874],{},"'HIHI'",[27,1876,1878,166,1880],{"className":1877},[30],[32,1879],{"disabled":34,"type":35},[56,1881,1882],{},"TypeError: decorator() missing 1 required positional argument: 'func'",[27,1884,1886,166,1888],{"className":1885},[30],[32,1887],{"disabled":34,"type":35},[56,1889,1890],{},"['hi', 'hi']",[60,1892,1893,1895,1901],{},[63,1894,65],{},[67,1896,1897,202,1899],{},[70,1898,72],{},[56,1900,1866],{},[67,1902,1903,166,1905,1908,1909,1912,1913,1916,1917,1919,1920,1923,1924,1927,1928,1930,1931,1934,1935,1938],{},[70,1904,78],{},[56,1906,1907],{},"repeat(times=2)"," returns ",[56,1910,1911],{},"decorator",", which is then applied to ",[56,1914,1915],{},"shout",", producing ",[56,1918,298],{},". Calling ",[56,1921,1922],{},"shout(\"hi\")"," actually calls ",[56,1925,1926],{},"wrapper(\"hi\")",", which invokes the original ",[56,1929,1922],{}," twice and collects the (already-uppercased) results into a list. Option C is the mistake of thinking ",[56,1932,1933],{},"@repeat(times=2)"," applies ",[56,1936,1937],{},"repeat"," directly to the function instead of first calling it to get a decorator.",[14,1940,1941,1945,2079,2133],{"language":310},[17,1942,1944],{"id":1943},"q15-which-is-the-idiomatic-most-robust-way-to-write-a-decorator-that-is-meant-to-work-on-any-function-signature","Q15. Which is the idiomatic, most robust way to write a decorator that is meant to work on any function signature?",[316,1946,1947],{"language":310},[319,1948,1950],{"className":321,"code":1949,"language":310,"meta":323,"style":323},"import functools\n\ndef timed(func):\n    @functools.wraps(func)\n    def wrapper(*args, **kwargs):\n        start = time.perf_counter()\n        try:\n            return func(*args, **kwargs)\n        finally:\n            print(f\"{func.__name__} took {time.perf_counter() - start:.4f}s\")\n    return wrapper\n",[56,1951,1952,1958,1962,1971,1978,1994,2004,2011,2025,2032,2073],{"__ignoreMap":323},[327,1953,1954,1956],{"class":329,"line":330},[327,1955,1691],{"class":346},[327,1957,1694],{"class":353},[327,1959,1960],{"class":329,"line":337},[327,1961,641],{"emptyLinePlaceholder":34},[327,1963,1964,1966,1969],{"class":329,"line":343},[327,1965,347],{"class":346},[327,1967,1968],{"class":333}," timed",[327,1970,1242],{"class":353},[327,1972,1973,1976],{"class":329,"line":357},[327,1974,1975],{"class":333},"    @functools.wraps",[327,1977,1725],{"class":353},[327,1979,1980,1982,1984,1986,1988,1990,1992],{"class":329,"line":644},[327,1981,855],{"class":346},[327,1983,1249],{"class":333},[327,1985,619],{"class":353},[327,1987,1254],{"class":346},[327,1989,1502],{"class":353},[327,1991,1505],{"class":346},[327,1993,1508],{"class":353},[327,1995,1996,1999,2001],{"class":329,"line":886},[327,1997,1998],{"class":353},"        start ",[327,2000,599],{"class":346},[327,2002,2003],{"class":353}," time.perf_counter()\n",[327,2005,2006,2009],{"class":329,"line":891},[327,2007,2008],{"class":346},"        try",[327,2010,1320],{"class":353},[327,2012,2013,2015,2017,2019,2021,2023],{"class":329,"line":1062},[327,2014,1784],{"class":346},[327,2016,1293],{"class":353},[327,2018,1254],{"class":346},[327,2020,1502],{"class":353},[327,2022,1505],{"class":346},[327,2024,1526],{"class":353},[327,2026,2027,2030],{"class":329,"line":1071},[327,2028,2029],{"class":346},"        finally",[327,2031,1320],{"class":353},[327,2033,2034,2037,2039,2041,2043,2045,2047,2049,2052,2054,2057,2060,2063,2066,2068,2071],{"class":329,"line":1076},[327,2035,2036],{"class":360},"            print",[327,2038,619],{"class":353},[327,2040,1267],{"class":346},[327,2042,1283],{"class":1270},[327,2044,1274],{"class":360},[327,2046,1277],{"class":353},[327,2048,1280],{"class":360},[327,2050,2051],{"class":1270}," took ",[327,2053,1274],{"class":360},[327,2055,2056],{"class":353},"time.perf_counter() ",[327,2058,2059],{"class":346},"-",[327,2061,2062],{"class":353}," start",[327,2064,2065],{"class":346},":.4f",[327,2067,1359],{"class":360},[327,2069,2070],{"class":1270},"s\"",[327,2072,1286],{"class":353},[327,2074,2075,2077],{"class":329,"line":1082},[327,2076,1065],{"class":346},[327,2078,1305],{"class":353},[22,2080,2082,2102,2114,2124],{"className":2081},[25],[27,2083,2085,2087,2088,2091,2092,2094,2095,2098,2099,2101],{"className":2084},[30],[32,2086],{"disabled":34,"type":35}," This version — ",[56,2089,2090],{},"*args, **kwargs"," forwards any signature, ",[56,2093,227],{}," preserves metadata, and ",[56,2096,2097],{},"finally"," ensures timing prints even if ",[56,2100,506],{}," raises",[27,2103,2105,2107,2108,2110,2111,2113],{"className":2104},[30],[32,2106],{"disabled":34,"type":35}," Same, but without ",[56,2109,227],{},", since it only affects ",[56,2112,302],{}," output and nothing functional",[27,2115,2117,2107,2119,255,2121,2123],{"className":2116},[30],[32,2118],{"disabled":34,"type":35},[56,2120,254],{},[56,2122,2097],{},", since decorators shouldn't handle control flow",[27,2125,2127,2129,2130,2132],{"className":2126},[30],[32,2128],{"disabled":34,"type":35}," Same, but replacing ",[56,2131,2090],{}," with the wrapped function's exact named parameters for clarity",[60,2134,2135,2137,2150],{},[63,2136,65],{},[67,2138,2139,2141,2142,2091,2144,2094,2146,2098,2148,2101],{},[70,2140,72],{}," A — This version — ",[56,2143,2090],{},[56,2145,227],{},[56,2147,2097],{},[56,2149,506],{},[67,2151,2152,166,2154,2157,2158,2160,2161,2163,2164,2166,2167,2170,2171,255,2173,2176],{},[70,2153,78],{},[70,2155,2156],{},"Idiom"," — A general-purpose decorator must accept and forward an arbitrary signature (",[56,2159,2090],{},"), preserve introspection metadata (",[56,2162,227],{},"), and use ",[56,2165,2097],{}," so cleanup\u002Flogging code runs even when the wrapped function raises. Skipping ",[56,2168,2169],{},"wraps"," (option B) is a real bug in production code — it breaks tools that rely on ",[56,2172,240],{},[56,2174,2175],{},"__qualname__",", such as API routers and test discovery. Hardcoding named parameters (option D) makes the decorator unreusable for any other function.",[14,2178,2179,2191,2228],{},[17,2180,2182,2183,2186,2187,2190],{"id":2181},"q16-a-decorator-needs-to-work-both-as-cache-no-parentheses-and-cachemaxsize100-with-parentheses-what-is-the-idiomatic-way-to-support-both-forms","Q16. A decorator needs to work both as ",[56,2184,2185],{},"@cache"," (no parentheses) and ",[56,2188,2189],{},"@cache(maxsize=100)"," (with parentheses). What is the idiomatic way to support both forms?",[22,2192,2194,2200,2209,2219],{"className":2193},[25],[27,2195,2197,2199],{"className":2196},[30],[32,2198],{"disabled":34,"type":35}," Detect whether the single positional argument is callable: if so, treat it as the direct-decoration case and decorate immediately; otherwise treat it as configuration and return a real decorator",[27,2201,2203,2205,2206,2208],{"className":2202},[30],[32,2204],{"disabled":34,"type":35}," Always require parentheses and document that ",[56,2207,2185],{}," alone is unsupported",[27,2210,2212,2214,2215,2218],{"className":2211},[30],[32,2213],{"disabled":34,"type":35}," Overload the function name with two different ",[56,2216,2217],{},"def cache"," definitions",[27,2220,2222,2224,2225,2227],{"className":2221},[30],[32,2223],{"disabled":34,"type":35}," Use ",[56,2226,1414],{}," and assume the first argument is always the function",[60,2229,2230,2232,2237],{},[63,2231,65],{},[67,2233,2234,2236],{},[70,2235,72],{}," A — Detect whether the single positional argument is callable: if so, treat it as the direct-decoration case and decorate immediately; otherwise treat it as configuration and return a real decorator",[67,2238,2239,166,2241,2243,2244,2247,2248,2251],{},[70,2240,78],{},[70,2242,2156],{}," — Libraries like ",[56,2245,2246],{},"functools.lru_cache"," (Python 3.8+) support this dual-mode pattern by checking ",[56,2249,2250],{},"callable(arg) and not kwargs"," to distinguish \"I was called directly on a function\" from \"I was called with configuration and need to return a decorator.\" Requiring parentheses always (option B) is simpler but breaks compatibility with existing dual-mode decorators users expect to work either way; redefining a function twice (option C) simply overwrites the first definition rather than overloading it.",[14,2253,2254,2258,2291],{},[17,2255,2257],{"id":2256},"q17-for-simple-memoization-of-a-pure-function-keyed-on-its-arguments-which-is-the-best-practice-choice","Q17. For simple memoization of a pure function keyed on its arguments, which is the best-practice choice?",[22,2259,2261,2269,2279,2285],{"className":2260},[25],[27,2262,2264,166,2266,2268],{"className":2263},[30],[32,2265],{"disabled":34,"type":35},[56,2267,2246],{},", since it's a battle-tested, thread-safe-for-reads standard-library decorator with eviction support",[27,2270,2272,2274,2275,2278],{"className":2271},[30],[32,2273],{"disabled":34,"type":35}," A hand-rolled decorator using a plain ",[56,2276,2277],{},"dict"," with no size limit, since it gives identical behavior with less import overhead",[27,2280,2282,2284],{"className":2281},[30],[32,2283],{"disabled":34,"type":35}," A global mutable list scanned linearly for matching arguments on every call",[27,2286,2288,2290],{"className":2287},[30],[32,2289],{"disabled":34,"type":35}," Re-running the function every time, since memoization is rarely worth the complexity",[60,2292,2293,2295,2301],{},[63,2294,65],{},[67,2296,2297,202,2299,2268],{},[70,2298,72],{},[56,2300,2246],{},[67,2302,2303,166,2305,1634,2307,2309,2310,2313,2314,2317],{},[70,2304,78],{},[70,2306,2156],{},[56,2308,2246],{}," handles hashable-argument caching, an optional ",[56,2311,2312],{},"maxsize"," for bounded memory, and ",[56,2315,2316],{},"cache_info()"," for introspection, all without extra code. A hand-rolled unbounded dict cache (option B) is a memory-leak risk in long-running processes since entries are never evicted; a linear-scan list (option C) is both slower and more error-prone at determining argument equality.",[14,2319,2320,2324,2361],{},[17,2321,2323],{"id":2322},"q18-in-modern-python-3-code-which-is-the-preferred-way-to-maintain-a-mutable-counter-shared-between-an-outer-function-and-its-inner-closures","Q18. In modern Python 3 code, which is the preferred way to maintain a mutable counter shared between an outer function and its inner closures?",[22,2325,2327,2335,2349,2355],{"className":2326},[25],[27,2328,2330,166,2332,2334],{"className":2329},[30],[32,2331],{"disabled":34,"type":35},[56,2333,82],{},", since it clearly expresses intent and avoids indirection through a container object",[27,2336,2338,2340,2341,2344,2345,2348],{"className":2337},[30],[32,2339],{"disabled":34,"type":35}," A single-element list, e.g. ",[56,2342,2343],{},"count = [0]",", mutated via ",[56,2346,2347],{},"count[0] += 1",", because closures can't rebind outer names at all",[27,2350,2352,2354],{"className":2351},[30],[32,2353],{"disabled":34,"type":35}," A global variable, since it's simplest",[27,2356,2358,2360],{"className":2357},[30],[32,2359],{"disabled":34,"type":35}," A mutable default argument on the inner function",[60,2362,2363,2365,2371],{},[63,2364,65],{},[67,2366,2367,202,2369,2334],{},[70,2368,72],{},[56,2370,82],{},[67,2372,2373,166,2375,1634,2377,2379,2380,2382],{},[70,2374,78],{},[70,2376,2156],{},[56,2378,82],{}," (Python 3+) directly and readably rebinds an enclosing-scope variable. The list-mutation trick (option B) was a common workaround in Python 2, which lacked ",[56,2381,82],{},", but it's now considered a code smell — it mutates a container instead of rebinding a name, which is harder to read and easy to mix up with genuinely shared mutable state. A global (option C) unnecessarily widens scope beyond the closure's own use case.",[14,2384,2385,2393,2538,2600],{"language":310},[17,2386,2388,2389,2392],{"id":2387},"q19-a-class-based-decorator-is-implemented-using-__call__-which-statement-about-it-is-correct","Q19. A class-based decorator is implemented using ",[56,2390,2391],{},"__call__",". Which statement about it is correct?",[316,2394,2395],{"language":310},[319,2396,2398],{"className":321,"code":2397,"language":310,"meta":323,"style":323},"import functools\n\nclass CountCalls:\n    def __init__(self, func):\n        functools.update_wrapper(self, func)\n        self.func = func\n        self.calls = 0\n\n    def __call__(self, *args, **kwargs):\n        self.calls += 1\n        return self.func(*args, **kwargs)\n\n@CountCalls\ndef greet():\n    return \"hi\"\n",[56,2399,2400,2406,2410,2419,2429,2439,2452,2463,2467,2485,2495,2513,2517,2522,2531],{"__ignoreMap":323},[327,2401,2402,2404],{"class":329,"line":330},[327,2403,1691],{"class":346},[327,2405,1694],{"class":353},[327,2407,2408],{"class":329,"line":337},[327,2409,641],{"emptyLinePlaceholder":34},[327,2411,2412,2414,2417],{"class":329,"line":343},[327,2413,1314],{"class":346},[327,2415,2416],{"class":333}," CountCalls",[327,2418,1320],{"class":353},[327,2420,2421,2423,2426],{"class":329,"line":357},[327,2422,855],{"class":346},[327,2424,2425],{"class":360}," __init__",[327,2427,2428],{"class":353},"(self, func):\n",[327,2430,2431,2434,2436],{"class":329,"line":644},[327,2432,2433],{"class":353},"        functools.update_wrapper(",[327,2435,1423],{"class":360},[327,2437,2438],{"class":353},", func)\n",[327,2440,2441,2444,2447,2449],{"class":329,"line":886},[327,2442,2443],{"class":360},"        self",[327,2445,2446],{"class":353},".func ",[327,2448,599],{"class":346},[327,2450,2451],{"class":353}," func\n",[327,2453,2454,2456,2459,2461],{"class":329,"line":891},[327,2455,2443],{"class":360},[327,2457,2458],{"class":353},".calls ",[327,2460,599],{"class":346},[327,2462,1026],{"class":360},[327,2464,2465],{"class":329,"line":1062},[327,2466,641],{"emptyLinePlaceholder":34},[327,2468,2469,2471,2474,2477,2479,2481,2483],{"class":329,"line":1071},[327,2470,855],{"class":346},[327,2472,2473],{"class":360}," __call__",[327,2475,2476],{"class":353},"(self, ",[327,2478,1254],{"class":346},[327,2480,1502],{"class":353},[327,2482,1505],{"class":346},[327,2484,1508],{"class":353},[327,2486,2487,2489,2491,2493],{"class":329,"line":1076},[327,2488,2443],{"class":360},[327,2490,2458],{"class":353},[327,2492,1050],{"class":346},[327,2494,878],{"class":360},[327,2496,2497,2499,2502,2505,2507,2509,2511],{"class":329,"line":1082},[327,2498,1057],{"class":346},[327,2500,2501],{"class":360}," self",[327,2503,2504],{"class":353},".func(",[327,2506,1254],{"class":346},[327,2508,1502],{"class":353},[327,2510,1505],{"class":346},[327,2512,1526],{"class":353},[327,2514,2515],{"class":329,"line":1092},[327,2516,641],{"emptyLinePlaceholder":34},[327,2518,2519],{"class":329,"line":1101},[327,2520,2521],{"class":333},"@CountCalls\n",[327,2523,2524,2526,2529],{"class":329,"line":1110},[327,2525,347],{"class":346},[327,2527,2528],{"class":333}," greet",[327,2530,354],{"class":353},[327,2532,2533,2535],{"class":329,"line":1119},[327,2534,1065],{"class":346},[327,2536,2537],{"class":1270}," \"hi\"\n",[22,2539,2541,2569,2575,2584],{"className":2540},[25],[27,2542,2544,166,2546,2549,2550,2552,2553,255,2555,2557,2558,2560,2561,2564,2565,2568],{"className":2543},[30],[32,2545],{"disabled":34,"type":35},[56,2547,2548],{},"functools.update_wrapper"," is needed here (the class-based equivalent of ",[56,2551,227],{},") to copy ",[56,2554,240],{},[56,2556,244],{}," from ",[56,2559,506],{}," onto the ",[56,2562,2563],{},"CountCalls"," instance, and ",[56,2566,2567],{},"greet.calls"," is now readable state that a plain closure-based decorator can't expose as cleanly",[27,2570,2572,2574],{"className":2571},[30],[32,2573],{"disabled":34,"type":35}," Class-based decorators can never preserve the original function's metadata, unlike function-based ones",[27,2576,2578,166,2580,2583],{"className":2577},[30],[32,2579],{"disabled":34,"type":35},[56,2581,2582],{},"@CountCalls"," is invalid syntax because decorators must be functions, not classes",[27,2585,2587,166,2589,2592,2593,2596,2597],{"className":2586},[30],[32,2588],{"disabled":34,"type":35},[56,2590,2591],{},"self.calls"," resets to ",[56,2594,2595],{},"0"," on every call to ",[56,2598,2599],{},"greet()",[60,2601,2602,2604,2622],{},[63,2603,65],{},[67,2605,2606,202,2608,2549,2610,2552,2612,255,2614,2557,2616,2560,2618,2564,2620,2568],{},[70,2607,72],{},[56,2609,2548],{},[56,2611,227],{},[56,2613,240],{},[56,2615,244],{},[56,2617,506],{},[56,2619,2563],{},[56,2621,2567],{},[67,2623,2624,166,2626,2628,2629,2631,2632,2634,2635,2637,2638,2640,2641,2644],{},[70,2625,78],{},[70,2627,2156],{}," — Any callable object can be a decorator; a class implementing ",[56,2630,2391],{}," is a common pattern when the decorator needs to hold persistent, easily-inspectable state (like a call counter) rather than hiding it in a closure cell. ",[56,2633,2548],{}," is the underlying function that ",[56,2636,227],{}," calls internally, applicable to any object, not just nested functions. ",[56,2639,2591],{}," persists across calls precisely because ",[56,2642,2643],{},"__init__"," runs once, at decoration time, not on every invocation.",[14,2646,2647,2659,2696,2743],{"language":310},[17,2648,2650,2651,2654,2655,2658],{"id":2649},"q20-a-web-framework-uses-approuteusers-above-login_required-above-the-view-function-what-does-the-ordering-imply-and-why-does-it-matter","Q20. A web framework uses ",[56,2652,2653],{},"@app.route(\"\u002Fusers\")"," above ",[56,2656,2657],{},"@login_required"," above the view function. What does the ordering imply, and why does it matter?",[316,2660,2661],{"language":310},[319,2662,2664],{"className":321,"code":2663,"language":310,"meta":323,"style":323},"@app.route(\"\u002Fusers\")\n@login_required\ndef list_users():\n    ...\n",[56,2665,2666,2678,2683,2692],{"__ignoreMap":323},[327,2667,2668,2671,2673,2676],{"class":329,"line":330},[327,2669,2670],{"class":333},"@app.route",[327,2672,619],{"class":353},[327,2674,2675],{"class":1270},"\"\u002Fusers\"",[327,2677,1286],{"class":353},[327,2679,2680],{"class":329,"line":337},[327,2681,2682],{"class":333},"@login_required\n",[327,2684,2685,2687,2690],{"class":329,"line":343},[327,2686,347],{"class":346},[327,2688,2689],{"class":333}," list_users",[327,2691,354],{"class":353},[327,2693,2694],{"class":329,"line":357},[327,2695,361],{"class":360},[22,2697,2699,2720,2726,2737],{"className":2698},[25],[27,2700,2702,166,2704,2707,2708,2711,2712,2715,2716,2719],{"className":2701},[30],[32,2703],{"disabled":34,"type":35},[56,2705,2706],{},"login_required"," wraps ",[56,2709,2710],{},"list_users"," first (closest decorator), so the auth check runs on every request before the route even reaches the view logic; reversing the order would let ",[56,2713,2714],{},"app.route"," register the ",[211,2717,2718],{},"unwrapped"," view, bypassing the auth check entirely",[27,2721,2723,2725],{"className":2722},[30],[32,2724],{"disabled":34,"type":35}," The order is purely stylistic; Python decorators are commutative regardless of which one is listed first",[27,2727,2729,166,2731,2733,2734,2736],{"className":2728},[30],[32,2730],{"disabled":34,"type":35},[56,2732,2714],{}," always executes before ",[56,2735,2706],{}," no matter the order written, because routing decorators have special priority",[27,2738,2740,2742],{"className":2739},[30],[32,2741],{"disabled":34,"type":35}," Both decorators wrap independently and neither can see the other's effect",[60,2744,2745,2747,2759],{},[63,2746,65],{},[67,2748,2749,202,2751,2707,2753,2711,2755,2715,2757,2719],{},[70,2750,72],{},[56,2752,2706],{},[56,2754,2710],{},[56,2756,2714],{},[211,2758,2718],{},[67,2760,2761,166,2763,2765,2766,241,2769,2771,2772,2775,2776,2778,2779,2781,2782,2784],{},[70,2762,78],{},[70,2764,2156],{}," — Since decoration is ",[56,2767,2768],{},"list_users = app.route(\"\u002Fusers\")(login_required(list_users))",[56,2770,2706],{}," produces the auth-checking wrapper first, and ",[211,2773,2774],{},"that"," wrapper is what gets registered as the route handler. If the order were flipped, ",[56,2777,2714],{}," would register the raw ",[56,2780,2710],{},", and ",[56,2783,2706],{}," would wrap a function that's no longer connected to any route — a real, security-relevant bug that decorator-order mistakes can cause in production frameworks like Flask.",[2786,2787,2788],"style",{},"html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}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 .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 .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}",{"title":323,"searchDepth":337,"depth":337,"links":2790},[2791,2792,2794,2796,2798,2799,2801,2802,2803,2805,2806,2807,2809,2810,2811,2812,2814,2815,2816,2818],{"id":19,"depth":343,"text":20},{"id":88,"depth":343,"text":2793},"Q2. What does the nonlocal keyword do?",{"id":148,"depth":343,"text":2795},"Q3. @my_decorator placed above def func(): ... is syntactic sugar for which statement?",{"id":223,"depth":343,"text":2797},"Q4. What does functools.wraps do when applied inside a decorator's wrapper function?",{"id":313,"depth":343,"text":314},{"id":454,"depth":343,"text":2800},"Q6. A decorator that itself accepts arguments, e.g. @retry(times=3), requires how many levels of nested functions?",{"id":526,"depth":343,"text":527},{"id":583,"depth":343,"text":584},{"id":741,"depth":343,"text":2804},"Q9. Which change correctly fixes the late-binding bug from Q8 so the output is [0, 1, 2]?",{"id":833,"depth":343,"text":834},{"id":993,"depth":343,"text":994},{"id":1216,"depth":343,"text":2808},"Q12. A decorator wraps an instance method but the wrapper is defined as def wrapper(*args): ... (no **kwargs). What breaks?",{"id":1472,"depth":343,"text":1473},{"id":1678,"depth":343,"text":1679},{"id":1943,"depth":343,"text":1944},{"id":2181,"depth":343,"text":2813},"Q16. A decorator needs to work both as @cache (no parentheses) and @cache(maxsize=100) (with parentheses). What is the idiomatic way to support both forms?",{"id":2256,"depth":343,"text":2257},{"id":2322,"depth":343,"text":2323},{"id":2387,"depth":343,"text":2817},"Q19. A class-based decorator is implemented using __call__. Which statement about it is correct?",{"id":2649,"depth":343,"text":2819},"Q20. A web framework uses @app.route(\"\u002Fusers\") above @login_required above the view function. What does the ordering imply, and why does it matter?","md",{},"\u002Fpython\u002F11-closures-and-decorators",{"title":5,"description":323},"python\u002F11-closures-and-decorators","t5aAd1jgmmSn4Z331F__BeogoSDggABhvVISnQish7Q",1787335398259]