[{"data":1,"prerenderedAt":1233},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-decorators-explained":3},{"id":4,"title":5,"body":6,"description":1219,"difficulty":1220,"extension":1221,"framework":1222,"frameworkSlug":48,"meta":1223,"navigation":279,"order":56,"path":1224,"qaPath":1225,"seo":1226,"stem":1227,"subtopic":1228,"topic":1229,"topicSlug":1230,"updated":1231,"__hash__":1232},"blog\u002Fblog\u002Fpython-decorators-explained.md","Python Decorators Explained — Wrapping Functions, functools.wraps, and Decorators with Arguments",{"type":7,"value":8,"toc":1208},"minimark",[9,14,32,36,43,205,231,235,242,318,338,342,353,490,499,503,519,713,728,732,743,918,940,944,951,1007,1014,1018,1021,1153,1172,1176,1204],[10,11,13],"h2",{"id":12},"python-decorators-explained","Python decorators, explained",[15,16,17,18,22,23,26,27,31],"p",{},"A decorator is one of Python's most distinctive features — and one interviewers reach for\nconstantly, because understanding it requires that functions are ",[19,20,21],"strong",{},"first-class objects",",\nthat you grasp ",[19,24,25],{},"closures",", and that you know what the ",[28,29,30],"code",{},"@"," syntax actually does. This\nguide builds the idea up from scratch and works through the patterns that trip people up:\npreserving metadata, parameterised decorators, class-based decorators, and stacking order.",[10,33,35],{"id":34},"what-a-decorator-actually-is","What a decorator actually is",[15,37,38,39,42],{},"A decorator is just ",[19,40,41],{},"a callable that takes a function and returns a function"," (usually a\nnew one that wraps the original). Because functions are first-class objects in Python, you\ncan pass them around, define them inside other functions, and return them.",[44,45,50],"pre",{"className":46,"code":47,"language":48,"meta":49,"style":49},"language-python shiki shiki-themes github-light github-dark","def log_calls(func):\n    def wrapper(*args, **kwargs):\n        print(f\"calling {func.__name__}\")\n        result = func(*args, **kwargs)   # call the original\n        print(f\"{func.__name__} returned {result!r}\")\n        return result\n    return wrapper                       # return the wrapped version\n","python","",[28,51,52,69,93,124,149,184,193],{"__ignoreMap":49},[53,54,57,61,65],"span",{"class":55,"line":56},"line",1,[53,58,60],{"class":59},"szBVR","def",[53,62,64],{"class":63},"sScJk"," log_calls",[53,66,68],{"class":67},"sVt8B","(func):\n",[53,70,72,75,78,81,84,87,90],{"class":55,"line":71},2,[53,73,74],{"class":59},"    def",[53,76,77],{"class":63}," wrapper",[53,79,80],{"class":67},"(",[53,82,83],{"class":59},"*",[53,85,86],{"class":67},"args, ",[53,88,89],{"class":59},"**",[53,91,92],{"class":67},"kwargs):\n",[53,94,96,100,102,105,109,112,115,118,121],{"class":55,"line":95},3,[53,97,99],{"class":98},"sj4cs","        print",[53,101,80],{"class":67},[53,103,104],{"class":59},"f",[53,106,108],{"class":107},"sZZnC","\"calling ",[53,110,111],{"class":98},"{",[53,113,114],{"class":67},"func.",[53,116,117],{"class":98},"__name__}",[53,119,120],{"class":107},"\"",[53,122,123],{"class":67},")\n",[53,125,127,130,133,136,138,140,142,145],{"class":55,"line":126},4,[53,128,129],{"class":67},"        result ",[53,131,132],{"class":59},"=",[53,134,135],{"class":67}," func(",[53,137,83],{"class":59},[53,139,86],{"class":67},[53,141,89],{"class":59},[53,143,144],{"class":67},"kwargs)   ",[53,146,148],{"class":147},"sJ8bj","# call the original\n",[53,150,152,154,156,158,160,162,164,166,169,171,174,177,180,182],{"class":55,"line":151},5,[53,153,99],{"class":98},[53,155,80],{"class":67},[53,157,104],{"class":59},[53,159,120],{"class":107},[53,161,111],{"class":98},[53,163,114],{"class":67},[53,165,117],{"class":98},[53,167,168],{"class":107}," returned ",[53,170,111],{"class":98},[53,172,173],{"class":67},"result",[53,175,176],{"class":59},"!r",[53,178,179],{"class":98},"}",[53,181,120],{"class":107},[53,183,123],{"class":67},[53,185,187,190],{"class":55,"line":186},6,[53,188,189],{"class":59},"        return",[53,191,192],{"class":67}," result\n",[53,194,196,199,202],{"class":55,"line":195},7,[53,197,198],{"class":59},"    return",[53,200,201],{"class":67}," wrapper                       ",[53,203,204],{"class":147},"# return the wrapped version\n",[15,206,207,210,211,214,215,218,219,222,223,226,227,230],{},[28,208,209],{},"wrapper"," is a ",[19,212,213],{},"closure",": it remembers ",[28,216,217],{},"func"," from the enclosing scope even after\n",[28,220,221],{},"log_calls"," has returned. The ",[28,224,225],{},"*args, **kwargs"," forwarding is what lets one wrapper handle\na function with ",[19,228,229],{},"any"," signature.",[10,232,234],{"id":233},"the-syntax-is-just-sugar","The @ syntax is just sugar",[15,236,237,238,241],{},"The ",[28,239,240],{},"@decorator"," line above a function is shorthand for reassigning the name to the\ndecorated version. These two snippets are identical:",[44,243,245],{"className":46,"code":244,"language":48,"meta":49,"style":49},"@log_calls\ndef add(a, b):\n    return a + b\n\n# is exactly the same as:\ndef add(a, b):\n    return a + b\nadd = log_calls(add)     # the @ line does this\n",[28,246,247,252,262,275,281,286,294,304],{"__ignoreMap":49},[53,248,249],{"class":55,"line":56},[53,250,251],{"class":63},"@log_calls\n",[53,253,254,256,259],{"class":55,"line":71},[53,255,60],{"class":59},[53,257,258],{"class":63}," add",[53,260,261],{"class":67},"(a, b):\n",[53,263,264,266,269,272],{"class":55,"line":95},[53,265,198],{"class":59},[53,267,268],{"class":67}," a ",[53,270,271],{"class":59},"+",[53,273,274],{"class":67}," b\n",[53,276,277],{"class":55,"line":126},[53,278,280],{"emptyLinePlaceholder":279},true,"\n",[53,282,283],{"class":55,"line":151},[53,284,285],{"class":147},"# is exactly the same as:\n",[53,287,288,290,292],{"class":55,"line":186},[53,289,60],{"class":59},[53,291,258],{"class":63},[53,293,261],{"class":67},[53,295,296,298,300,302],{"class":55,"line":195},[53,297,198],{"class":59},[53,299,268],{"class":67},[53,301,271],{"class":59},[53,303,274],{"class":67},[53,305,307,310,312,315],{"class":55,"line":306},8,[53,308,309],{"class":67},"add ",[53,311,132],{"class":59},[53,313,314],{"class":67}," log_calls(add)     ",[53,316,317],{"class":147},"# the @ line does this\n",[15,319,320,321,324,325,327,328,331,332,334,335,337],{},"So ",[28,322,323],{},"add"," no longer points at the original function — it points at ",[28,326,209],{},". That's the\nwhole trick: decoration happens ",[19,329,330],{},"once, at definition time",", and every later call to\n",[28,333,323],{}," actually runs ",[28,336,209],{},".",[10,339,341],{"id":340},"preserving-metadata-with-functoolswraps","Preserving metadata with functools.wraps",[15,343,344,345,348,349,352],{},"There's a hidden cost to the version above: ",[28,346,347],{},"add.__name__"," is now ",[28,350,351],{},"\"wrapper\""," and its\ndocstring is gone, because the name is bound to the wrapper, not the original. This breaks\nintrospection, debuggers, and documentation tools.",[44,354,356],{"className":46,"code":355,"language":48,"meta":49,"style":49},"import functools\n\ndef log_calls(func):\n    @functools.wraps(func)        # copy __name__, __doc__, __wrapped__, etc.\n    def wrapper(*args, **kwargs):\n        return func(*args, **kwargs)\n    return wrapper\n\n@log_calls\ndef add(a, b):\n    \"\"\"Add two numbers.\"\"\"\n    return a + b\n\nadd.__name__   # 'add'  (without @wraps it would be 'wrapper')\nadd.__doc__    # 'Add two numbers.'\n",[28,357,358,366,370,378,389,405,420,427,431,436,445,451,462,467,479],{"__ignoreMap":49},[53,359,360,363],{"class":55,"line":56},[53,361,362],{"class":59},"import",[53,364,365],{"class":67}," functools\n",[53,367,368],{"class":55,"line":71},[53,369,280],{"emptyLinePlaceholder":279},[53,371,372,374,376],{"class":55,"line":95},[53,373,60],{"class":59},[53,375,64],{"class":63},[53,377,68],{"class":67},[53,379,380,383,386],{"class":55,"line":126},[53,381,382],{"class":63},"    @functools.wraps",[53,384,385],{"class":67},"(func)        ",[53,387,388],{"class":147},"# copy __name__, __doc__, __wrapped__, etc.\n",[53,390,391,393,395,397,399,401,403],{"class":55,"line":151},[53,392,74],{"class":59},[53,394,77],{"class":63},[53,396,80],{"class":67},[53,398,83],{"class":59},[53,400,86],{"class":67},[53,402,89],{"class":59},[53,404,92],{"class":67},[53,406,407,409,411,413,415,417],{"class":55,"line":186},[53,408,189],{"class":59},[53,410,135],{"class":67},[53,412,83],{"class":59},[53,414,86],{"class":67},[53,416,89],{"class":59},[53,418,419],{"class":67},"kwargs)\n",[53,421,422,424],{"class":55,"line":195},[53,423,198],{"class":59},[53,425,426],{"class":67}," wrapper\n",[53,428,429],{"class":55,"line":306},[53,430,280],{"emptyLinePlaceholder":279},[53,432,434],{"class":55,"line":433},9,[53,435,251],{"class":63},[53,437,439,441,443],{"class":55,"line":438},10,[53,440,60],{"class":59},[53,442,258],{"class":63},[53,444,261],{"class":67},[53,446,448],{"class":55,"line":447},11,[53,449,450],{"class":107},"    \"\"\"Add two numbers.\"\"\"\n",[53,452,454,456,458,460],{"class":55,"line":453},12,[53,455,198],{"class":59},[53,457,268],{"class":67},[53,459,271],{"class":59},[53,461,274],{"class":67},[53,463,465],{"class":55,"line":464},13,[53,466,280],{"emptyLinePlaceholder":279},[53,468,470,473,476],{"class":55,"line":469},14,[53,471,472],{"class":67},"add.",[53,474,475],{"class":98},"__name__",[53,477,478],{"class":147},"   # 'add'  (without @wraps it would be 'wrapper')\n",[53,480,482,484,487],{"class":55,"line":481},15,[53,483,472],{"class":67},[53,485,486],{"class":98},"__doc__",[53,488,489],{"class":147},"    # 'Add two numbers.'\n",[15,491,492,498],{},[19,493,494,495],{},"Always use ",[28,496,497],{},"functools.wraps"," on the wrapper. It's the single most common thing missing\nfrom a hand-written decorator.",[10,500,502],{"id":501},"decorators-that-take-arguments","Decorators that take arguments",[15,504,505,506,509,510,513,514,518],{},"To configure a decorator (e.g. ",[28,507,508],{},"@retry(times=3)","), you need ",[19,511,512],{},"one more layer",": a function\nthat takes the arguments and ",[515,516,517],"em",{},"returns a decorator",". So a parameterised decorator is a\nfunction returning a function returning a function.",[44,520,522],{"className":46,"code":521,"language":48,"meta":49,"style":49},"import functools\n\ndef retry(times):\n    def decorator(func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            for attempt in range(times):\n                try:\n                    return func(*args, **kwargs)\n                except Exception:\n                    if attempt == times - 1:\n                        raise            # last attempt — give up\n        return wrapper\n    return decorator\n\n@retry(times=3)          # retry(3) runs first, returns the real decorator\ndef fetch():\n    ...\n",[28,523,524,530,534,544,553,561,578,594,602,617,627,648,656,662,669,673,696,707],{"__ignoreMap":49},[53,525,526,528],{"class":55,"line":56},[53,527,362],{"class":59},[53,529,365],{"class":67},[53,531,532],{"class":55,"line":71},[53,533,280],{"emptyLinePlaceholder":279},[53,535,536,538,541],{"class":55,"line":95},[53,537,60],{"class":59},[53,539,540],{"class":63}," retry",[53,542,543],{"class":67},"(times):\n",[53,545,546,548,551],{"class":55,"line":126},[53,547,74],{"class":59},[53,549,550],{"class":63}," decorator",[53,552,68],{"class":67},[53,554,555,558],{"class":55,"line":151},[53,556,557],{"class":63},"        @functools.wraps",[53,559,560],{"class":67},"(func)\n",[53,562,563,566,568,570,572,574,576],{"class":55,"line":186},[53,564,565],{"class":59},"        def",[53,567,77],{"class":63},[53,569,80],{"class":67},[53,571,83],{"class":59},[53,573,86],{"class":67},[53,575,89],{"class":59},[53,577,92],{"class":67},[53,579,580,583,586,589,592],{"class":55,"line":195},[53,581,582],{"class":59},"            for",[53,584,585],{"class":67}," attempt ",[53,587,588],{"class":59},"in",[53,590,591],{"class":98}," range",[53,593,543],{"class":67},[53,595,596,599],{"class":55,"line":306},[53,597,598],{"class":59},"                try",[53,600,601],{"class":67},":\n",[53,603,604,607,609,611,613,615],{"class":55,"line":433},[53,605,606],{"class":59},"                    return",[53,608,135],{"class":67},[53,610,83],{"class":59},[53,612,86],{"class":67},[53,614,89],{"class":59},[53,616,419],{"class":67},[53,618,619,622,625],{"class":55,"line":438},[53,620,621],{"class":59},"                except",[53,623,624],{"class":98}," Exception",[53,626,601],{"class":67},[53,628,629,632,634,637,640,643,646],{"class":55,"line":447},[53,630,631],{"class":59},"                    if",[53,633,585],{"class":67},[53,635,636],{"class":59},"==",[53,638,639],{"class":67}," times ",[53,641,642],{"class":59},"-",[53,644,645],{"class":98}," 1",[53,647,601],{"class":67},[53,649,650,653],{"class":55,"line":453},[53,651,652],{"class":59},"                        raise",[53,654,655],{"class":147},"            # last attempt — give up\n",[53,657,658,660],{"class":55,"line":464},[53,659,189],{"class":59},[53,661,426],{"class":67},[53,663,664,666],{"class":55,"line":469},[53,665,198],{"class":59},[53,667,668],{"class":67}," decorator\n",[53,670,671],{"class":55,"line":481},[53,672,280],{"emptyLinePlaceholder":279},[53,674,676,679,681,685,687,690,693],{"class":55,"line":675},16,[53,677,678],{"class":63},"@retry",[53,680,80],{"class":67},[53,682,684],{"class":683},"s4XuR","times",[53,686,132],{"class":59},[53,688,689],{"class":98},"3",[53,691,692],{"class":67},")          ",[53,694,695],{"class":147},"# retry(3) runs first, returns the real decorator\n",[53,697,699,701,704],{"class":55,"line":698},17,[53,700,60],{"class":59},[53,702,703],{"class":63}," fetch",[53,705,706],{"class":67},"():\n",[53,708,710],{"class":55,"line":709},18,[53,711,712],{"class":98},"    ...\n",[15,714,715,716,719,720,723,724,727],{},"Read it outside-in: ",[28,717,718],{},"retry(times=3)"," is called immediately and returns ",[28,721,722],{},"decorator",", which\nis then applied to ",[28,725,726],{},"fetch",". That extra layer is the only structural difference from a\nplain decorator.",[10,729,731],{"id":730},"class-based-decorators","Class-based decorators",[15,733,734,735,738,739,742],{},"A class works as a decorator if its instances are ",[19,736,737],{},"callable"," (it defines ",[28,740,741],{},"__call__",").\nThis is handy when the decorator needs to hold state across calls.",[44,744,746],{"className":46,"code":745,"language":48,"meta":49,"style":49},"import functools\n\nclass CountCalls:\n    def __init__(self, func):\n        functools.update_wrapper(self, func)   # the @wraps equivalent for classes\n        self.func = func\n        self.count = 0\n    def __call__(self, *args, **kwargs):\n        self.count += 1\n        print(f\"call #{self.count}\")\n        return self.func(*args, **kwargs)\n\n@CountCalls\ndef greet():\n    print(\"hi\")\n",[28,747,748,754,758,768,778,792,805,817,835,847,870,888,892,897,906],{"__ignoreMap":49},[53,749,750,752],{"class":55,"line":56},[53,751,362],{"class":59},[53,753,365],{"class":67},[53,755,756],{"class":55,"line":71},[53,757,280],{"emptyLinePlaceholder":279},[53,759,760,763,766],{"class":55,"line":95},[53,761,762],{"class":59},"class",[53,764,765],{"class":63}," CountCalls",[53,767,601],{"class":67},[53,769,770,772,775],{"class":55,"line":126},[53,771,74],{"class":59},[53,773,774],{"class":98}," __init__",[53,776,777],{"class":67},"(self, func):\n",[53,779,780,783,786,789],{"class":55,"line":151},[53,781,782],{"class":67},"        functools.update_wrapper(",[53,784,785],{"class":98},"self",[53,787,788],{"class":67},", func)   ",[53,790,791],{"class":147},"# the @wraps equivalent for classes\n",[53,793,794,797,800,802],{"class":55,"line":186},[53,795,796],{"class":98},"        self",[53,798,799],{"class":67},".func ",[53,801,132],{"class":59},[53,803,804],{"class":67}," func\n",[53,806,807,809,812,814],{"class":55,"line":195},[53,808,796],{"class":98},[53,810,811],{"class":67},".count ",[53,813,132],{"class":59},[53,815,816],{"class":98}," 0\n",[53,818,819,821,824,827,829,831,833],{"class":55,"line":306},[53,820,74],{"class":59},[53,822,823],{"class":98}," __call__",[53,825,826],{"class":67},"(self, ",[53,828,83],{"class":59},[53,830,86],{"class":67},[53,832,89],{"class":59},[53,834,92],{"class":67},[53,836,837,839,841,844],{"class":55,"line":433},[53,838,796],{"class":98},[53,840,811],{"class":67},[53,842,843],{"class":59},"+=",[53,845,846],{"class":98}," 1\n",[53,848,849,851,853,855,858,861,864,866,868],{"class":55,"line":438},[53,850,99],{"class":98},[53,852,80],{"class":67},[53,854,104],{"class":59},[53,856,857],{"class":107},"\"call #",[53,859,860],{"class":98},"{self",[53,862,863],{"class":67},".count",[53,865,179],{"class":98},[53,867,120],{"class":107},[53,869,123],{"class":67},[53,871,872,874,877,880,882,884,886],{"class":55,"line":447},[53,873,189],{"class":59},[53,875,876],{"class":98}," self",[53,878,879],{"class":67},".func(",[53,881,83],{"class":59},[53,883,86],{"class":67},[53,885,89],{"class":59},[53,887,419],{"class":67},[53,889,890],{"class":55,"line":453},[53,891,280],{"emptyLinePlaceholder":279},[53,893,894],{"class":55,"line":464},[53,895,896],{"class":63},"@CountCalls\n",[53,898,899,901,904],{"class":55,"line":469},[53,900,60],{"class":59},[53,902,903],{"class":63}," greet",[53,905,706],{"class":67},[53,907,908,911,913,916],{"class":55,"line":481},[53,909,910],{"class":98},"    print",[53,912,80],{"class":67},[53,914,915],{"class":107},"\"hi\"",[53,917,123],{"class":67},[15,919,920,921,924,925,928,929,932,933,935,936,939],{},"Here ",[28,922,923],{},"greet"," becomes a ",[28,926,927],{},"CountCalls"," instance; calling ",[28,930,931],{},"greet()"," runs ",[28,934,741],{},". Use a\nclass when state (like ",[28,937,938],{},"count",") is cleaner as an attribute than as a closure variable.",[10,941,943],{"id":942},"stacking-decorators-and-order","Stacking decorators and order",[15,945,946,947,950],{},"Multiple decorators apply ",[19,948,949],{},"bottom-up"," — the one closest to the function wraps first, and\nthe topmost runs outermost at call time.",[44,952,954],{"className":46,"code":953,"language":48,"meta":49,"style":49},"@bold          # applied last, runs first at call time (outermost)\n@italic        # applied first, wraps the original\ndef text():\n    return \"hi\"\n\n# equivalent to:\ntext = bold(italic(text))\n",[28,955,956,964,972,981,988,992,997],{"__ignoreMap":49},[53,957,958,961],{"class":55,"line":56},[53,959,960],{"class":63},"@bold",[53,962,963],{"class":147},"          # applied last, runs first at call time (outermost)\n",[53,965,966,969],{"class":55,"line":71},[53,967,968],{"class":63},"@italic",[53,970,971],{"class":147},"        # applied first, wraps the original\n",[53,973,974,976,979],{"class":55,"line":95},[53,975,60],{"class":59},[53,977,978],{"class":63}," text",[53,980,706],{"class":67},[53,982,983,985],{"class":55,"line":126},[53,984,198],{"class":59},[53,986,987],{"class":107}," \"hi\"\n",[53,989,990],{"class":55,"line":151},[53,991,280],{"emptyLinePlaceholder":279},[53,993,994],{"class":55,"line":186},[53,995,996],{"class":147},"# equivalent to:\n",[53,998,999,1002,1004],{"class":55,"line":195},[53,1000,1001],{"class":67},"text ",[53,1003,132],{"class":59},[53,1005,1006],{"class":67}," bold(italic(text))\n",[15,1008,1009,1010,1013],{},"So the decoration order is bottom-to-top, but the ",[19,1011,1012],{},"execution"," order at call time is\ntop-to-bottom (outermost first). Getting this backwards is a classic interview slip.",[10,1015,1017],{"id":1016},"where-decorators-earn-their-keep","Where decorators earn their keep",[15,1019,1020],{},"Real-world decorators centralise cross-cutting concerns so the wrapped function stays\nfocused:",[44,1022,1024],{"className":46,"code":1023,"language":48,"meta":49,"style":49},"import functools, time\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",[28,1025,1026,1033,1037,1046,1052,1068,1078,1085,1100,1107,1147],{"__ignoreMap":49},[53,1027,1028,1030],{"class":55,"line":56},[53,1029,362],{"class":59},[53,1031,1032],{"class":67}," functools, time\n",[53,1034,1035],{"class":55,"line":71},[53,1036,280],{"emptyLinePlaceholder":279},[53,1038,1039,1041,1044],{"class":55,"line":95},[53,1040,60],{"class":59},[53,1042,1043],{"class":63}," timed",[53,1045,68],{"class":67},[53,1047,1048,1050],{"class":55,"line":126},[53,1049,382],{"class":63},[53,1051,560],{"class":67},[53,1053,1054,1056,1058,1060,1062,1064,1066],{"class":55,"line":151},[53,1055,74],{"class":59},[53,1057,77],{"class":63},[53,1059,80],{"class":67},[53,1061,83],{"class":59},[53,1063,86],{"class":67},[53,1065,89],{"class":59},[53,1067,92],{"class":67},[53,1069,1070,1073,1075],{"class":55,"line":186},[53,1071,1072],{"class":67},"        start ",[53,1074,132],{"class":59},[53,1076,1077],{"class":67}," time.perf_counter()\n",[53,1079,1080,1083],{"class":55,"line":195},[53,1081,1082],{"class":59},"        try",[53,1084,601],{"class":67},[53,1086,1087,1090,1092,1094,1096,1098],{"class":55,"line":306},[53,1088,1089],{"class":59},"            return",[53,1091,135],{"class":67},[53,1093,83],{"class":59},[53,1095,86],{"class":67},[53,1097,89],{"class":59},[53,1099,419],{"class":67},[53,1101,1102,1105],{"class":55,"line":433},[53,1103,1104],{"class":59},"        finally",[53,1106,601],{"class":67},[53,1108,1109,1112,1114,1116,1118,1120,1122,1124,1127,1129,1132,1134,1137,1140,1142,1145],{"class":55,"line":438},[53,1110,1111],{"class":98},"            print",[53,1113,80],{"class":67},[53,1115,104],{"class":59},[53,1117,120],{"class":107},[53,1119,111],{"class":98},[53,1121,114],{"class":67},[53,1123,117],{"class":98},[53,1125,1126],{"class":107}," took ",[53,1128,111],{"class":98},[53,1130,1131],{"class":67},"time.perf_counter() ",[53,1133,642],{"class":59},[53,1135,1136],{"class":67}," start",[53,1138,1139],{"class":59},":.4f",[53,1141,179],{"class":98},[53,1143,1144],{"class":107},"s\"",[53,1146,123],{"class":67},[53,1148,1149,1151],{"class":55,"line":447},[53,1150,198],{"class":59},[53,1152,426],{"class":67},[15,1154,1155,1156,1159,1160,1163,1164,1167,1168,1171],{},"Common uses: ",[28,1157,1158],{},"functools.lru_cache"," for ",[19,1161,1162],{},"memoisation",", ",[28,1165,1166],{},"@property"," for computed\nattributes, framework routing (",[28,1169,1170],{},"@app.route(...)","), authentication checks, logging, timing,\nand retries — anywhere you'd otherwise copy-paste boilerplate around many functions.",[10,1173,1175],{"id":1174},"recap","Recap",[15,1177,1178,1179,1182,1183,1186,1187,1190,1191,1193,1194,1196,1197,1200,1201,1203],{},"A decorator is ",[19,1180,1181],{},"a callable that takes a function and returns a (usually wrapping)\nfunction",", and ",[28,1184,1185],{},"@deco"," is just sugar for ",[28,1188,1189],{},"func = deco(func)"," at definition time. Forward\n",[28,1192,225],{}," so the wrapper handles any signature, and always apply\n",[28,1195,497],{}," to preserve the original's name and docstring. A decorator that takes\narguments needs an ",[19,1198,1199],{},"extra layer"," (a function returning a decorator); a class with\n",[28,1202,741],{}," works when you want stateful decoration. Stacked decorators apply bottom-up and\nexecute outermost-first. Master the closure underneath and decorators stop feeling like\nmagic.",[1205,1206,1207],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":49,"searchDepth":71,"depth":71,"links":1209},[1210,1211,1212,1213,1214,1215,1216,1217,1218],{"id":12,"depth":71,"text":13},{"id":34,"depth":71,"text":35},{"id":233,"depth":71,"text":234},{"id":340,"depth":71,"text":341},{"id":501,"depth":71,"text":502},{"id":730,"depth":71,"text":731},{"id":942,"depth":71,"text":943},{"id":1016,"depth":71,"text":1017},{"id":1174,"depth":71,"text":1175},"How Python decorators work — the @ syntax as sugar for wrapping, why you need functools.wraps, decorators that take arguments, class-based decorators, and stacking order.","hard","md","Python",{},"\u002Fblog\u002Fpython-decorators-explained","\u002Fpython\u002Ffunctions\u002Fdecorators",{"title":5,"description":1219},"blog\u002Fpython-decorators-explained","Decorators","Functions","functions","2026-06-19","EDl8egVk2lZoc-leaPMVe9aZVAKovnkgeGU7qO4ho8s",1781808673080]