[{"data":1,"prerenderedAt":904},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-functools-explained":3},{"id":4,"title":5,"body":6,"description":891,"difficulty":892,"extension":893,"framework":894,"frameworkSlug":60,"meta":895,"navigation":88,"order":68,"path":896,"qaPath":897,"seo":898,"stem":899,"subtopic":20,"topic":900,"topicSlug":901,"updated":902,"__hash__":903},"blog\u002Fblog\u002Fpython-functools-explained.md","Python functools Explained — lru_cache, partial, reduce, wraps, and cached_property",{"type":7,"value":8,"toc":881},"minimark",[9,14,45,49,55,202,223,227,232,350,356,360,372,443,449,453,464,611,618,622,627,793,800,804,833,837,877],[10,11,13],"h2",{"id":12},"python-functools-explained","Python functools, explained",[15,16,17,21,22,26,27,30,31,30,34,30,37,40,41,44],"p",{},[18,19,20],"code",{},"functools"," is the standard library's toolbox for ",[23,24,25],"strong",{},"higher-order functions"," — utilities\nthat take or return functions. A handful of its tools show up constantly in real code and\ninterviews: ",[18,28,29],{},"lru_cache",", ",[18,32,33],{},"partial",[18,35,36],{},"reduce",[18,38,39],{},"wraps",", and ",[18,42,43],{},"cached_property",". Here's what\neach does and when to reach for it.",[10,46,48],{"id":47},"lru_cache-memoisation-for-free","lru_cache — memoisation for free",[15,50,51,52,54],{},"Decorating a function with ",[18,53,29],{}," stores results keyed by arguments, so repeated calls\nwith the same inputs return instantly. It turns exponential recursion into linear time.",[56,57,62],"pre",{"className":58,"code":59,"language":60,"meta":61,"style":61},"language-python shiki shiki-themes github-light github-dark","from functools import lru_cache\n\n@lru_cache(maxsize=None)\ndef fib(n):\n    return n if n \u003C 2 else fib(n - 1) + fib(n - 2)\n\nfib(100)            # fast — each subproblem computed once\nfib.cache_info()    # CacheInfo(hits=..., misses=101, maxsize=None, currsize=101)\n","python","",[18,63,64,83,90,114,126,172,177,193],{"__ignoreMap":61},[65,66,69,73,77,80],"span",{"class":67,"line":68},"line",1,[65,70,72],{"class":71},"szBVR","from",[65,74,76],{"class":75},"sVt8B"," functools ",[65,78,79],{"class":71},"import",[65,81,82],{"class":75}," lru_cache\n",[65,84,86],{"class":67,"line":85},2,[65,87,89],{"emptyLinePlaceholder":88},true,"\n",[65,91,93,97,100,104,107,111],{"class":67,"line":92},3,[65,94,96],{"class":95},"sScJk","@lru_cache",[65,98,99],{"class":75},"(",[65,101,103],{"class":102},"s4XuR","maxsize",[65,105,106],{"class":71},"=",[65,108,110],{"class":109},"sj4cs","None",[65,112,113],{"class":75},")\n",[65,115,117,120,123],{"class":67,"line":116},4,[65,118,119],{"class":71},"def",[65,121,122],{"class":95}," fib",[65,124,125],{"class":75},"(n):\n",[65,127,129,132,135,138,140,143,146,149,152,155,158,161,164,166,168,170],{"class":67,"line":128},5,[65,130,131],{"class":71},"    return",[65,133,134],{"class":75}," n ",[65,136,137],{"class":71},"if",[65,139,134],{"class":75},[65,141,142],{"class":71},"\u003C",[65,144,145],{"class":109}," 2",[65,147,148],{"class":71}," else",[65,150,151],{"class":75}," fib(n ",[65,153,154],{"class":71},"-",[65,156,157],{"class":109}," 1",[65,159,160],{"class":75},") ",[65,162,163],{"class":71},"+",[65,165,151],{"class":75},[65,167,154],{"class":71},[65,169,145],{"class":109},[65,171,113],{"class":75},[65,173,175],{"class":67,"line":174},6,[65,176,89],{"emptyLinePlaceholder":88},[65,178,180,183,186,189],{"class":67,"line":179},7,[65,181,182],{"class":75},"fib(",[65,184,185],{"class":109},"100",[65,187,188],{"class":75},")            ",[65,190,192],{"class":191},"sJ8bj","# fast — each subproblem computed once\n",[65,194,196,199],{"class":67,"line":195},8,[65,197,198],{"class":75},"fib.cache_info()    ",[65,200,201],{"class":191},"# CacheInfo(hits=..., misses=101, maxsize=None, currsize=101)\n",[15,203,204,206,207,210,211,214,215,218,219,222],{},[18,205,103],{}," bounds the cache (least-recently-used items evicted). Arguments must be\n",[23,208,209],{},"hashable",", and the cached function should be ",[23,212,213],{},"pure"," — caching a function with side\neffects or that depends on external state will give stale results. Python 3.9+ adds\n",[18,216,217],{},"@cache"," as shorthand for ",[18,220,221],{},"lru_cache(maxsize=None)",".",[10,224,226],{"id":225},"partial-pre-fill-arguments","partial — pre-fill arguments",[15,228,229,231],{},[18,230,33],{}," builds a new callable with some arguments already supplied. It's a clean\nalternative to lambdas for \"the same function but with this argument fixed.\"",[56,233,235],{"className":58,"code":234,"language":60,"meta":61,"style":61},"from functools import partial\n\ndef power(base, exp):\n    return base ** exp\n\nsquare = partial(power, exp=2)\ncube = partial(power, exp=3)\n\nsquare(5)    # 25\ncube(5)      # 125\n",[18,236,237,248,252,262,275,279,299,317,321,336],{"__ignoreMap":61},[65,238,239,241,243,245],{"class":67,"line":68},[65,240,72],{"class":71},[65,242,76],{"class":75},[65,244,79],{"class":71},[65,246,247],{"class":75}," partial\n",[65,249,250],{"class":67,"line":85},[65,251,89],{"emptyLinePlaceholder":88},[65,253,254,256,259],{"class":67,"line":92},[65,255,119],{"class":71},[65,257,258],{"class":95}," power",[65,260,261],{"class":75},"(base, exp):\n",[65,263,264,266,269,272],{"class":67,"line":116},[65,265,131],{"class":71},[65,267,268],{"class":75}," base ",[65,270,271],{"class":71},"**",[65,273,274],{"class":75}," exp\n",[65,276,277],{"class":67,"line":128},[65,278,89],{"emptyLinePlaceholder":88},[65,280,281,284,286,289,292,294,297],{"class":67,"line":174},[65,282,283],{"class":75},"square ",[65,285,106],{"class":71},[65,287,288],{"class":75}," partial(power, ",[65,290,291],{"class":102},"exp",[65,293,106],{"class":71},[65,295,296],{"class":109},"2",[65,298,113],{"class":75},[65,300,301,304,306,308,310,312,315],{"class":67,"line":179},[65,302,303],{"class":75},"cube ",[65,305,106],{"class":71},[65,307,288],{"class":75},[65,309,291],{"class":102},[65,311,106],{"class":71},[65,313,314],{"class":109},"3",[65,316,113],{"class":75},[65,318,319],{"class":67,"line":195},[65,320,89],{"emptyLinePlaceholder":88},[65,322,324,327,330,333],{"class":67,"line":323},9,[65,325,326],{"class":75},"square(",[65,328,329],{"class":109},"5",[65,331,332],{"class":75},")    ",[65,334,335],{"class":191},"# 25\n",[65,337,339,342,344,347],{"class":67,"line":338},10,[65,340,341],{"class":75},"cube(",[65,343,329],{"class":109},[65,345,346],{"class":75},")      ",[65,348,349],{"class":191},"# 125\n",[15,351,352,353,222],{},"Common use: adapting a function to a callback API that passes fewer arguments, or building\nspecialised versions like ",[18,354,355],{},"int_base2 = partial(int, base=2)",[10,357,359],{"id":358},"reduce-fold-a-sequence-to-one-value","reduce — fold a sequence to one value",[15,361,362,364,365,30,368,371],{},[18,363,36],{}," repeatedly applies a two-argument function across an iterable, accumulating a single\nresult. It's the general form behind ",[18,366,367],{},"sum",[18,369,370],{},"max",", and friends.",[56,373,375],{"className":58,"code":374,"language":60,"meta":61,"style":61},"from functools import reduce\n\nproduct = reduce(lambda acc, x: acc * x, [1, 2, 3, 4], 1)   # 24\n",[18,376,377,388,392],{"__ignoreMap":61},[65,378,379,381,383,385],{"class":67,"line":68},[65,380,72],{"class":71},[65,382,76],{"class":75},[65,384,79],{"class":71},[65,386,387],{"class":102}," reduce\n",[65,389,390],{"class":67,"line":85},[65,391,89],{"emptyLinePlaceholder":88},[65,393,394,397,399,402,404,407,410,413,416,419,421,423,425,427,429,432,435,437,440],{"class":67,"line":92},[65,395,396],{"class":75},"product ",[65,398,106],{"class":71},[65,400,401],{"class":102}," reduce",[65,403,99],{"class":75},[65,405,406],{"class":71},"lambda",[65,408,409],{"class":75}," acc, x: acc ",[65,411,412],{"class":71},"*",[65,414,415],{"class":75}," x, [",[65,417,418],{"class":109},"1",[65,420,30],{"class":75},[65,422,296],{"class":109},[65,424,30],{"class":75},[65,426,314],{"class":109},[65,428,30],{"class":75},[65,430,431],{"class":109},"4",[65,433,434],{"class":75},"], ",[65,436,418],{"class":109},[65,438,439],{"class":75},")   ",[65,441,442],{"class":191},"# 24\n",[15,444,445,446,448],{},"The third argument is the initial value (and the result for an empty iterable). Prefer a\nplain loop or built-in when one exists — ",[18,447,36],{}," is most justified for genuine folds like\nrunning a chain of compositions.",[10,450,452],{"id":451},"wraps-keep-the-wrapped-functions-identity","wraps — keep the wrapped function's identity",[15,454,455,456,459,460,463],{},"When you write a decorator, the wrapper replaces the original, losing its ",[18,457,458],{},"__name__"," and\ndocstring. ",[18,461,462],{},"functools.wraps"," copies that metadata back.",[56,465,467],{"className":58,"code":466,"language":60,"meta":61,"style":61},"from functools import wraps\n\ndef log(func):\n    @wraps(func)                # without this, greet.__name__ == 'wrapper'\n    def wrapper(*args, **kwargs):\n        print(f\"calling {func.__name__}\")\n        return func(*args, **kwargs)\n    return wrapper\n\n@log\ndef greet(): \"say hi\"\ngreet.__name__   # 'greet'\n",[18,468,469,480,484,494,505,525,553,570,577,581,586,600],{"__ignoreMap":61},[65,470,471,473,475,477],{"class":67,"line":68},[65,472,72],{"class":71},[65,474,76],{"class":75},[65,476,79],{"class":71},[65,478,479],{"class":75}," wraps\n",[65,481,482],{"class":67,"line":85},[65,483,89],{"emptyLinePlaceholder":88},[65,485,486,488,491],{"class":67,"line":92},[65,487,119],{"class":71},[65,489,490],{"class":95}," log",[65,492,493],{"class":75},"(func):\n",[65,495,496,499,502],{"class":67,"line":116},[65,497,498],{"class":95},"    @wraps",[65,500,501],{"class":75},"(func)                ",[65,503,504],{"class":191},"# without this, greet.__name__ == 'wrapper'\n",[65,506,507,510,513,515,517,520,522],{"class":67,"line":128},[65,508,509],{"class":71},"    def",[65,511,512],{"class":95}," wrapper",[65,514,99],{"class":75},[65,516,412],{"class":71},[65,518,519],{"class":75},"args, ",[65,521,271],{"class":71},[65,523,524],{"class":75},"kwargs):\n",[65,526,527,530,532,535,539,542,545,548,551],{"class":67,"line":174},[65,528,529],{"class":109},"        print",[65,531,99],{"class":75},[65,533,534],{"class":71},"f",[65,536,538],{"class":537},"sZZnC","\"calling ",[65,540,541],{"class":109},"{",[65,543,544],{"class":75},"func.",[65,546,547],{"class":109},"__name__}",[65,549,550],{"class":537},"\"",[65,552,113],{"class":75},[65,554,555,558,561,563,565,567],{"class":67,"line":179},[65,556,557],{"class":71},"        return",[65,559,560],{"class":75}," func(",[65,562,412],{"class":71},[65,564,519],{"class":75},[65,566,271],{"class":71},[65,568,569],{"class":75},"kwargs)\n",[65,571,572,574],{"class":67,"line":195},[65,573,131],{"class":71},[65,575,576],{"class":75}," wrapper\n",[65,578,579],{"class":67,"line":323},[65,580,89],{"emptyLinePlaceholder":88},[65,582,583],{"class":67,"line":338},[65,584,585],{"class":95},"@log\n",[65,587,589,591,594,597],{"class":67,"line":588},11,[65,590,119],{"class":71},[65,592,593],{"class":95}," greet",[65,595,596],{"class":75},"(): ",[65,598,599],{"class":537},"\"say hi\"\n",[65,601,603,606,608],{"class":67,"line":602},12,[65,604,605],{"class":75},"greet.",[65,607,458],{"class":109},[65,609,610],{"class":191},"   # 'greet'\n",[15,612,613,614,617],{},"Always apply ",[18,615,616],{},"@wraps"," in your decorators — it keeps introspection, debuggers, and docs\nworking.",[10,619,621],{"id":620},"cached_property-compute-once-per-instance","cached_property — compute once per instance",[15,623,624,626],{},[18,625,43],{}," turns an expensive method into an attribute computed on first access and\nthen stored on the instance, so later accesses are free.",[56,628,630],{"className":58,"code":629,"language":60,"meta":61,"style":61},"from functools import cached_property\n\nclass Dataset:\n    def __init__(self, rows):\n        self.rows = rows\n\n    @cached_property\n    def stats(self):\n        print(\"computing...\")\n        return {\"count\": len(self.rows), \"total\": sum(self.rows)}\n\nd = Dataset([1, 2, 3])\nd.stats     # \"computing...\" then the dict\nd.stats     # cached — no recompute\n",[18,631,632,643,647,658,668,681,685,690,700,711,749,753,776,785],{"__ignoreMap":61},[65,633,634,636,638,640],{"class":67,"line":68},[65,635,72],{"class":71},[65,637,76],{"class":75},[65,639,79],{"class":71},[65,641,642],{"class":75}," cached_property\n",[65,644,645],{"class":67,"line":85},[65,646,89],{"emptyLinePlaceholder":88},[65,648,649,652,655],{"class":67,"line":92},[65,650,651],{"class":71},"class",[65,653,654],{"class":95}," Dataset",[65,656,657],{"class":75},":\n",[65,659,660,662,665],{"class":67,"line":116},[65,661,509],{"class":71},[65,663,664],{"class":109}," __init__",[65,666,667],{"class":75},"(self, rows):\n",[65,669,670,673,676,678],{"class":67,"line":128},[65,671,672],{"class":109},"        self",[65,674,675],{"class":75},".rows ",[65,677,106],{"class":71},[65,679,680],{"class":75}," rows\n",[65,682,683],{"class":67,"line":174},[65,684,89],{"emptyLinePlaceholder":88},[65,686,687],{"class":67,"line":179},[65,688,689],{"class":95},"    @cached_property\n",[65,691,692,694,697],{"class":67,"line":195},[65,693,509],{"class":71},[65,695,696],{"class":95}," stats",[65,698,699],{"class":75},"(self):\n",[65,701,702,704,706,709],{"class":67,"line":323},[65,703,529],{"class":109},[65,705,99],{"class":75},[65,707,708],{"class":537},"\"computing...\"",[65,710,113],{"class":75},[65,712,713,715,718,721,724,727,729,732,735,738,740,742,744,746],{"class":67,"line":338},[65,714,557],{"class":71},[65,716,717],{"class":75}," {",[65,719,720],{"class":537},"\"count\"",[65,722,723],{"class":75},": ",[65,725,726],{"class":109},"len",[65,728,99],{"class":75},[65,730,731],{"class":109},"self",[65,733,734],{"class":75},".rows), ",[65,736,737],{"class":537},"\"total\"",[65,739,723],{"class":75},[65,741,367],{"class":109},[65,743,99],{"class":75},[65,745,731],{"class":109},[65,747,748],{"class":75},".rows)}\n",[65,750,751],{"class":67,"line":588},[65,752,89],{"emptyLinePlaceholder":88},[65,754,755,758,760,763,765,767,769,771,773],{"class":67,"line":602},[65,756,757],{"class":75},"d ",[65,759,106],{"class":71},[65,761,762],{"class":75}," Dataset([",[65,764,418],{"class":109},[65,766,30],{"class":75},[65,768,296],{"class":109},[65,770,30],{"class":75},[65,772,314],{"class":109},[65,774,775],{"class":75},"])\n",[65,777,779,782],{"class":67,"line":778},13,[65,780,781],{"class":75},"d.stats     ",[65,783,784],{"class":191},"# \"computing...\" then the dict\n",[65,786,788,790],{"class":67,"line":787},14,[65,789,781],{"class":75},[65,791,792],{"class":191},"# cached — no recompute\n",[15,794,795,796,799],{},"Because the result is stored in the instance ",[18,797,798],{},"__dict__",", it lives as long as the instance.\nUse it for derived values that are costly and don't change.",[10,801,803],{"id":802},"reduce-vs-comprehensions-and-other-tools","reduce vs comprehensions, and other tools",[15,805,806,808,809,812,813,816,817,820,821,824,825,828,829,832],{},[18,807,20],{}," also offers ",[18,810,811],{},"singledispatch"," (function overloading by argument type),\n",[18,814,815],{},"total_ordering"," (fill in comparison methods from ",[18,818,819],{},"__eq__"," and one of ",[18,822,823],{},"__lt__"," etc.), and\n",[18,826,827],{},"cmp_to_key"," (adapt old-style comparison functions for ",[18,830,831],{},"sorted","). These are niche but handy\nwhen they fit.",[10,834,836],{"id":835},"recap","Recap",[15,838,839,841,842,850,851,855,856,860,861,865,866,870,871,873,874,876],{},[18,840,20],{}," packages the most useful higher-order helpers: ",[23,843,844,846,847],{},[18,845,29],{},"\u002F",[18,848,849],{},"cache"," memoise\npure, hashable-argument functions; ",[23,852,853],{},[18,854,33],{}," pre-fills arguments to make specialised\ncallables; ",[23,857,858],{},[18,859,36],{}," folds an iterable to a single value; ",[23,862,863],{},[18,864,39],{}," preserves a wrapped\nfunction's name and docstring inside decorators; and ",[23,867,868],{},[18,869,43],{}," computes an\nexpensive attribute once per instance. Round it out with ",[18,872,811],{}," and\n",[18,875,815],{}," when you need type-based dispatch or auto-generated comparisons.",[878,879,880],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":61,"searchDepth":85,"depth":85,"links":882},[883,884,885,886,887,888,889,890],{"id":12,"depth":85,"text":13},{"id":47,"depth":85,"text":48},{"id":225,"depth":85,"text":226},{"id":358,"depth":85,"text":359},{"id":451,"depth":85,"text":452},{"id":620,"depth":85,"text":621},{"id":802,"depth":85,"text":803},{"id":835,"depth":85,"text":836},"A practical tour of Python's functools module — memoising with lru_cache and cache, building specialised callables with partial, reduce, preserving metadata with wraps, and cached_property.","medium","md","Python",{},"\u002Fblog\u002Fpython-functools-explained","\u002Fpython\u002Ffunctional\u002Ffunctools",{"title":5,"description":891},"blog\u002Fpython-functools-explained","Functional Programming","functional","2026-06-19","tDwYeBBDBq7YzXWxsTfryGaNvFwxTmnh0pvYr5viiU4",1782244092083]