[{"data":1,"prerenderedAt":2495},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-let-and-lambda-formulas-from-python":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-let-and-lambda-formulas-from-python":2460,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-let-and-lambda-formulas-from-python":2469,"children:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-let-and-lambda-formulas-from-python":2474,"related:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-let-and-lambda-formulas-from-python":2475},{"id":4,"title":5,"body":6,"dateModified":2426,"datePublished":2426,"description":2427,"extension":2428,"faq":2429,"keyword":2438,"kind":2439,"meta":2440,"navigation":439,"path":2449,"seo":2450,"slug":2453,"stem":2454,"topics":2455,"type":2458,"__hash__":2459},"docs\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-let-and-lambda-formulas-from-python\u002Findex.md","Write LET and LAMBDA Formulas from Python",{"type":7,"value":8,"toc":2413},"minimark",[9,52,57,85,102,106,115,171,196,404,408,411,672,678,682,694,1173,1181,1433,1437,1443,1638,1692,1781,1796,1800,1810,2093,2106,2221,2225,2252,2256,2322,2326,2348,2352,2358,2364,2370,2376,2380,2409],[10,11,12,16,17,20,21,24,25,28,29,32,33,36,37,40,41,45,46,51],"p",{},[13,14,15],"code",{},"LET"," gives names to intermediate results inside a formula — ",[13,18,19],{},"=LET(net, A2*(1-B2), net + net*C2)"," computes ",[13,22,23],{},"net"," once and reads like a small program. ",[13,26,27],{},"LAMBDA"," goes further and turns a formula into a reusable function that a workbook can name, such as ",[13,30,31],{},"=GrossUp(A2, B2)",". Both are Excel 365 functions, and both are among the easiest formulas to get wrong from Python: type ",[13,34,35],{},"=LET(...)"," into openpyxl exactly as you would in Excel, open the file, and the cell shows ",[13,38,39],{},"#NAME?",". The formula is fine; what is missing is how Excel ",[42,43,44],"em",{},"stores"," newer functions and their parameters inside the file. This page, part of ",[47,48,50],"a",{"href":49},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002F","Working with Excel Formulas in Python",", shows the stored form, a helper that produces it, and how to check the result.",[53,54,56],"h2",{"id":55},"prerequisites","Prerequisites",[58,59,64],"pre",{"className":60,"code":61,"language":62,"meta":63,"style":63},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl xlsxwriter\n","bash","",[13,65,66],{"__ignoreMap":63},[67,68,71,75,79,82],"span",{"class":69,"line":70},"line",1,[67,72,74],{"class":73},"sMTad","pip",[67,76,78],{"class":77},"srMev"," install",[67,80,81],{"class":77}," openpyxl",[67,83,84],{"class":77}," xlsxwriter\n",[10,86,87,89,90,92,93,95,96,98,99,101],{},[13,88,15],{}," needs Excel 2021 or Microsoft 365 and ",[13,91,27],{}," needs Microsoft 365; older versions show ",[13,94,39],{}," whatever you do. LibreOffice 24.8 and later calculate ",[13,97,15],{}," but not ",[13,100,27],{},", which is enough to test half of this page on a machine without Excel.",[53,103,105],{"id":104},"why-the-formula-shows-name","Why the formula shows #NAME?",[10,107,108,109,111,112,114],{},"Excel's file format predates these functions. To stay readable by older versions, Excel writes every function added after 2007 with a prefix, and writes the names you introduce inside ",[13,110,15],{}," and ",[13,113,27],{}," with another one:",[116,117,118,131],"table",{},[119,120,121],"thead",{},[122,123,124,128],"tr",{},[125,126,127],"th",{},"You type in Excel",[125,129,130],{},"Stored in the file",[132,133,134,147,159],"tbody",{},[122,135,136,142],{},[137,138,139],"td",{},[13,140,141],{},"=LET(net, A2*(1-B2), net*2)",[137,143,144],{},[13,145,146],{},"=_xlfn.LET(_xlpm.net,A2*(1-B2),_xlpm.net*2)",[122,148,149,154],{},[137,150,151],{},[13,152,153],{},"=LAMBDA(x, x*2)(A2)",[137,155,156],{},[13,157,158],{},"=_xlfn.LAMBDA(_xlpm.x,_xlpm.x*2)(A2)",[122,160,161,166],{},[137,162,163],{},[13,164,165],{},"=XLOOKUP(A2, D:D, E:E)",[137,167,168],{},[13,169,170],{},"=_xlfn.XLOOKUP(A2,D:D,E:E)",[10,172,173,174,176,177,179,180,182,183,186,187,190,191,195],{},"When Excel shows you the formula it strips the prefixes; when it saves, it adds them back. openpyxl and xlsxwriter write your text as it is, so text without the prefixes reaches Excel as a call to an unknown function called ",[13,175,15],{},", and a reference to an undefined name called ",[13,178,23],{},". The same rule is behind ",[13,181,39],{}," on ",[13,184,185],{},"XLOOKUP",", ",[13,188,189],{},"FILTER"," and friends — see ",[47,192,194],{"href":193},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-name-error-for-new-excel-functions-written-by-python\u002F","Fix #NAME? Errors for New Excel Functions Written by Python",".",[197,198,206,207,206,211,206,215,206,223,206,233,206,240,206,249,206,252,206,307,206,315,206,318,206,322,206,324,206,374,206,381,206,385,206,388,206,392,206,395,206,398,206,400],"svg",{"viewBox":199,"role":200,"ariaLabelledBy":201,"xmlns":204,"style":205},"4 0 752 156","img",[202,203],"let-stored-t","let-stored-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[208,209,210],"title",{"id":202},"What you type and what the file must contain",[212,213,214],"desc",{"id":203},"Two lines of formula text. The first is the LET formula as typed in Excel; the second is the stored form with _xlfn. before LET and _xlpm. before each use of the parameter name net.",[216,217],"rect",{"x":218,"y":219,"width":220,"height":221,"fill":222},"4","0","752","156","#ffffff",[216,224],{"x":225,"y":226,"width":227,"height":228,"rx":229,"fill":230,"stroke":231,"style":232},"20.0","16.0","720.0","66.0","12","#f0f2f5","var(--line,#cdd5e6)","stroke-width:1.5px",[234,235,239],"text",{"x":236,"y":237,"style":238},"60.0","47.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","1",[241,242],"circle",{"cx":243,"cy":244,"r":245,"fill":246,"stroke":247,"style":248},"36.0","42.5","11.0","#5b6780","none","stroke-width:0px",[234,250,239],{"x":243,"y":237,"style":251},"font-size:12px;font-weight:700;fill:#ffffff;text-anchor:middle",[234,253,256,261,263,266,268,271,274,277,280,282,285,288,291,294,296,298,300,302,305],{"x":254,"y":237,"style":255},"82.0","font-size:12.5px;text-anchor:start;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",[257,258,260],"tspan",{"style":259},"fill:var(--text,#172033);font-weight:400","=",[257,262,15],{"style":259},[257,264,265],{"style":259},"(",[257,267,23],{"style":259},[257,269,270],{"style":259},",",[257,272,273],{"style":259}," ",[257,275,276],{"style":259},"A2",[257,278,279],{"style":259},"*",[257,281,265],{"style":259},[257,283,239],{"style":284},"fill:var(--gold-ink,#7a4e06);font-weight:400",[257,286,287],{"style":259},"-",[257,289,290],{"style":259},"B2",[257,292,293],{"style":259},")",[257,295,270],{"style":259},[257,297,273],{"style":259},[257,299,23],{"style":259},[257,301,279],{"style":259},[257,303,304],{"style":284},"2",[257,306,293],{"style":259},[216,308],{"x":309,"y":310,"width":311,"height":312,"rx":218,"fill":313,"stroke":314,"style":248},"24.0","53.0","712.0","21.0","#d9f4f1","var(--teal,#0f9488)",[234,316,304],{"x":236,"y":317,"style":238},"68.0",[241,319],{"cx":243,"cy":320,"r":245,"fill":321,"stroke":247,"style":248},"63.5","#0b6157",[234,323,304],{"x":243,"y":317,"style":251},[234,325,326,328,331,333,335,337,340,342,344,346,348,350,352,354,356,358,360,362,364,366,368,370,372],{"x":254,"y":317,"style":255},[257,327,260],{"style":259},[257,329,330],{"style":259},"_xlfn",[257,332,195],{"style":259},[257,334,15],{"style":259},[257,336,265],{"style":259},[257,338,339],{"style":259},"_xlpm",[257,341,195],{"style":259},[257,343,23],{"style":259},[257,345,270],{"style":259},[257,347,276],{"style":259},[257,349,279],{"style":259},[257,351,265],{"style":259},[257,353,239],{"style":284},[257,355,287],{"style":259},[257,357,290],{"style":259},[257,359,293],{"style":259},[257,361,270],{"style":259},[257,363,339],{"style":259},[257,365,195],{"style":259},[257,367,23],{"style":259},[257,369,279],{"style":259},[257,371,304],{"style":284},[257,373,293],{"style":259},[216,375],{"x":225,"y":376,"width":377,"height":378,"rx":379,"fill":380,"stroke":231,"style":232},"96.0","353.0","44.0","10","#e7ebef",[241,382],{"cx":383,"cy":384,"r":245,"fill":246,"stroke":247,"style":248},"40.0","118.0",[234,386,239],{"x":383,"y":387,"style":251},"122.5",[234,389,391],{"x":236,"y":387,"style":390},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","what Excel shows in the formula bar",[216,393],{"x":394,"y":376,"width":377,"height":378,"rx":379,"fill":313,"stroke":314,"style":232},"387.0",[241,396],{"cx":397,"cy":384,"r":245,"fill":321,"stroke":247,"style":248},"407.0",[234,399,304],{"x":397,"y":387,"style":251},[234,401,403],{"x":402,"y":387,"style":390},"427.0","what openpyxl or xlsxwriter must write",[53,405,407],{"id":406},"step-1-write-a-let-formula-with-openpyxl","Step 1 — Write a LET formula with openpyxl",[10,409,410],{},"Write the stored form directly. The workbook below computes a price after discount and then adds tax to it, naming the intermediate value once:",[58,412,416],{"className":413,"code":414,"language":415,"meta":63,"style":63},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\n\nwb = Workbook()\nws = wb.active\nws.append([\"List price\", \"Discount\", \"Tax rate\", \"Gross\"])\nfor row in [[100, 0.2, 0.25], [80, 0.1, 0.25], [250, 0.0, 0.1]]:\n    ws.append(row)\nfor r in range(2, 5):\n    ws[f\"D{r}\"] = f\"=_xlfn.LET(_xlpm.net,A{r}*(1-B{r}),_xlpm.net+_xlpm.net*C{r})\"\nwb.save(\"let_openpyxl.xlsx\")\nprint(ws[\"D2\"].value)\n","python",[13,417,418,434,441,452,463,490,551,557,582,645,657],{"__ignoreMap":63},[67,419,420,424,428,431],{"class":69,"line":70},[67,421,423],{"class":422},"s-kum","from",[67,425,427],{"class":426},"skGVy"," openpyxl ",[67,429,430],{"class":422},"import",[67,432,433],{"class":426}," Workbook\n",[67,435,437],{"class":69,"line":436},2,[67,438,440],{"emptyLinePlaceholder":439},true,"\n",[67,442,444,447,449],{"class":69,"line":443},3,[67,445,446],{"class":426},"wb ",[67,448,260],{"class":422},[67,450,451],{"class":426}," Workbook()\n",[67,453,455,458,460],{"class":69,"line":454},4,[67,456,457],{"class":426},"ws ",[67,459,260],{"class":422},[67,461,462],{"class":426}," wb.active\n",[67,464,466,469,472,474,477,479,482,484,487],{"class":69,"line":465},5,[67,467,468],{"class":426},"ws.append([",[67,470,471],{"class":77},"\"List price\"",[67,473,186],{"class":426},[67,475,476],{"class":77},"\"Discount\"",[67,478,186],{"class":426},[67,480,481],{"class":77},"\"Tax rate\"",[67,483,186],{"class":426},[67,485,486],{"class":77},"\"Gross\"",[67,488,489],{"class":426},"])\n",[67,491,493,496,499,502,505,509,511,514,516,519,522,525,527,530,532,534,536,539,541,544,546,548],{"class":69,"line":492},6,[67,494,495],{"class":422},"for",[67,497,498],{"class":426}," row ",[67,500,501],{"class":422},"in",[67,503,504],{"class":426}," [[",[67,506,508],{"class":507},"sP0c6","100",[67,510,186],{"class":426},[67,512,513],{"class":507},"0.2",[67,515,186],{"class":426},[67,517,518],{"class":507},"0.25",[67,520,521],{"class":426},"], [",[67,523,524],{"class":507},"80",[67,526,186],{"class":426},[67,528,529],{"class":507},"0.1",[67,531,186],{"class":426},[67,533,518],{"class":507},[67,535,521],{"class":426},[67,537,538],{"class":507},"250",[67,540,186],{"class":426},[67,542,543],{"class":507},"0.0",[67,545,186],{"class":426},[67,547,529],{"class":507},[67,549,550],{"class":426},"]]:\n",[67,552,554],{"class":69,"line":553},7,[67,555,556],{"class":426},"    ws.append(row)\n",[67,558,560,562,565,567,570,572,574,576,579],{"class":69,"line":559},8,[67,561,495],{"class":422},[67,563,564],{"class":426}," r ",[67,566,501],{"class":422},[67,568,569],{"class":507}," range",[67,571,265],{"class":426},[67,573,304],{"class":507},[67,575,186],{"class":426},[67,577,578],{"class":507},"5",[67,580,581],{"class":426},"):\n",[67,583,585,588,591,594,598,601,604,607,610,612,615,618,620,622,624,627,629,631,633,636,638,640,642],{"class":69,"line":584},9,[67,586,587],{"class":426},"    ws[",[67,589,590],{"class":422},"f",[67,592,593],{"class":77},"\"D",[67,595,597],{"class":596},"sSjpA","{",[67,599,600],{"class":426},"r",[67,602,603],{"class":596},"}",[67,605,606],{"class":77},"\"",[67,608,609],{"class":426},"] ",[67,611,260],{"class":422},[67,613,614],{"class":422}," f",[67,616,617],{"class":77},"\"=_xlfn.LET(_xlpm.net,A",[67,619,597],{"class":596},[67,621,600],{"class":426},[67,623,603],{"class":596},[67,625,626],{"class":77},"*(1-B",[67,628,597],{"class":596},[67,630,600],{"class":426},[67,632,603],{"class":596},[67,634,635],{"class":77},"),_xlpm.net+_xlpm.net*C",[67,637,597],{"class":596},[67,639,600],{"class":426},[67,641,603],{"class":596},[67,643,644],{"class":77},")\"\n",[67,646,648,651,654],{"class":69,"line":647},10,[67,649,650],{"class":426},"wb.save(",[67,652,653],{"class":77},"\"let_openpyxl.xlsx\"",[67,655,656],{"class":426},")\n",[67,658,660,663,666,669],{"class":69,"line":659},11,[67,661,662],{"class":507},"print",[67,664,665],{"class":426},"(ws[",[67,667,668],{"class":77},"\"D2\"",[67,670,671],{"class":426},"].value)\n",[10,673,674,675,677],{},"Every occurrence of the parameter needs the prefix — its definition and each use. Missing one gives ",[13,676,39],{}," in Excel for that reference only, which can be hard to spot in a long formula. Spaces after commas are allowed but not needed; Excel stores the formula without them.",[53,679,681],{"id":680},"step-2-generate-the-prefixes-instead-of-typing-them","Step 2 — Generate the prefixes instead of typing them",[10,683,684,685,688,689,111,691,693],{},"Typing ",[13,686,687],{},"_xlpm."," by hand invites mistakes. A small helper can take the readable form and produce the stored one: it prefixes ",[13,690,15],{},[13,692,27],{},", finds the parameter names they declare, and prefixes every use of those names that is not part of a cell reference, a sheet name or a string:",[58,695,697],{"className":413,"code":696,"language":415,"meta":63,"style":63},"import re\n\nNEW_FUNCS = (\"LET\", \"LAMBDA\", \"XLOOKUP\", \"FILTER\", \"SORT\", \"UNIQUE\", \"SEQUENCE\", \"TEXTJOIN\", \"IFS\", \"SWITCH\")\n\ndef to_stored(formula):\n    text = formula.replace(\", \", \",\")\n    params = set()\n    for m in re.finditer(r\"\\bLET\\(([^()]*)\", text):\n        parts = m.group(1).split(\",\")\n        params.update(p.strip() for p in parts[0:-1:2])\n    for m in re.finditer(r\"\\bLAMBDA\\(([^()]*)\", text):\n        params.update(p.strip() for p in m.group(1).split(\",\")[:-1])\n    for name in sorted(params, key=len, reverse=True):\n        text = re.sub(rf'(?\u003C![\\w.!\"$])\\b{re.escape(name)}\\b(?![\\w(!])', f\"_xlpm.{name}\", text)\n    for fn in NEW_FUNCS:\n        text = re.sub(rf\"(?\u003C![\\w.]){fn}\\(\", f\"_xlfn.{fn}(\", text)\n    return text\n\nprint(to_stored(\"=LET(net, A2*(1-B2), net + net*C2)\"))\nprint(to_stored(\"=LET(rate, C2, gross, A2*(1+rate), ROUND(gross, 2))\"))\nprint(to_stored(\"=LAMBDA(x, rate, x\u002F(1-rate))(A2, B2)\"))\n",[13,698,699,706,710,771,775,787,807,820,867,886,916,952,980,1017,1063,1079,1121,1130,1135,1149,1161],{"__ignoreMap":63},[67,700,701,703],{"class":69,"line":70},[67,702,430],{"class":422},[67,704,705],{"class":426}," re\n",[67,707,708],{"class":69,"line":436},[67,709,440],{"emptyLinePlaceholder":439},[67,711,712,715,718,721,724,726,729,731,734,736,739,741,744,746,749,751,754,756,759,761,764,766,769],{"class":69,"line":443},[67,713,714],{"class":507},"NEW_FUNCS",[67,716,717],{"class":422}," =",[67,719,720],{"class":426}," (",[67,722,723],{"class":77},"\"LET\"",[67,725,186],{"class":426},[67,727,728],{"class":77},"\"LAMBDA\"",[67,730,186],{"class":426},[67,732,733],{"class":77},"\"XLOOKUP\"",[67,735,186],{"class":426},[67,737,738],{"class":77},"\"FILTER\"",[67,740,186],{"class":426},[67,742,743],{"class":77},"\"SORT\"",[67,745,186],{"class":426},[67,747,748],{"class":77},"\"UNIQUE\"",[67,750,186],{"class":426},[67,752,753],{"class":77},"\"SEQUENCE\"",[67,755,186],{"class":426},[67,757,758],{"class":77},"\"TEXTJOIN\"",[67,760,186],{"class":426},[67,762,763],{"class":77},"\"IFS\"",[67,765,186],{"class":426},[67,767,768],{"class":77},"\"SWITCH\"",[67,770,656],{"class":426},[67,772,773],{"class":69,"line":454},[67,774,440],{"emptyLinePlaceholder":439},[67,776,777,780,784],{"class":69,"line":465},[67,778,779],{"class":422},"def",[67,781,783],{"class":782},"s_Opv"," to_stored",[67,785,786],{"class":426},"(formula):\n",[67,788,789,792,794,797,800,802,805],{"class":69,"line":492},[67,790,791],{"class":426},"    text ",[67,793,260],{"class":422},[67,795,796],{"class":426}," formula.replace(",[67,798,799],{"class":77},"\", \"",[67,801,186],{"class":426},[67,803,804],{"class":77},"\",\"",[67,806,656],{"class":426},[67,808,809,812,814,817],{"class":69,"line":553},[67,810,811],{"class":426},"    params ",[67,813,260],{"class":422},[67,815,816],{"class":507}," set",[67,818,819],{"class":426},"()\n",[67,821,822,825,828,830,833,835,837,840,842,846,849,852,855,858,860,862,864],{"class":69,"line":559},[67,823,824],{"class":422},"    for",[67,826,827],{"class":426}," m ",[67,829,501],{"class":422},[67,831,832],{"class":426}," re.finditer(",[67,834,600],{"class":422},[67,836,606],{"class":77},[67,838,839],{"class":507},"\\b",[67,841,15],{"class":77},[67,843,845],{"class":844},"s_b0D","\\(",[67,847,848],{"class":507},"([",[67,850,851],{"class":422},"^",[67,853,854],{"class":596},"()",[67,856,857],{"class":507},"]",[67,859,279],{"class":422},[67,861,293],{"class":507},[67,863,606],{"class":77},[67,865,866],{"class":426},", text):\n",[67,868,869,872,874,877,879,882,884],{"class":69,"line":584},[67,870,871],{"class":426},"        parts ",[67,873,260],{"class":422},[67,875,876],{"class":426}," m.group(",[67,878,239],{"class":507},[67,880,881],{"class":426},").split(",[67,883,804],{"class":77},[67,885,656],{"class":426},[67,887,888,891,893,896,898,901,903,906,908,910,912,914],{"class":69,"line":647},[67,889,890],{"class":426},"        params.update(p.strip() ",[67,892,495],{"class":422},[67,894,895],{"class":426}," p ",[67,897,501],{"class":422},[67,899,900],{"class":426}," parts[",[67,902,219],{"class":507},[67,904,905],{"class":426},":",[67,907,287],{"class":422},[67,909,239],{"class":507},[67,911,905],{"class":426},[67,913,304],{"class":507},[67,915,489],{"class":426},[67,917,918,920,922,924,926,928,930,932,934,936,938,940,942,944,946,948,950],{"class":69,"line":659},[67,919,824],{"class":422},[67,921,827],{"class":426},[67,923,501],{"class":422},[67,925,832],{"class":426},[67,927,600],{"class":422},[67,929,606],{"class":77},[67,931,839],{"class":507},[67,933,27],{"class":77},[67,935,845],{"class":844},[67,937,848],{"class":507},[67,939,851],{"class":422},[67,941,854],{"class":596},[67,943,857],{"class":507},[67,945,279],{"class":422},[67,947,293],{"class":507},[67,949,606],{"class":77},[67,951,866],{"class":426},[67,953,955,957,959,961,963,965,967,969,971,974,976,978],{"class":69,"line":954},12,[67,956,890],{"class":426},[67,958,495],{"class":422},[67,960,895],{"class":426},[67,962,501],{"class":422},[67,964,876],{"class":426},[67,966,239],{"class":507},[67,968,881],{"class":426},[67,970,804],{"class":77},[67,972,973],{"class":426},")[:",[67,975,287],{"class":422},[67,977,239],{"class":507},[67,979,489],{"class":426},[67,981,983,985,988,990,993,996,1000,1002,1005,1007,1010,1012,1015],{"class":69,"line":982},13,[67,984,824],{"class":422},[67,986,987],{"class":426}," name ",[67,989,501],{"class":422},[67,991,992],{"class":507}," sorted",[67,994,995],{"class":426},"(params, ",[67,997,999],{"class":998},"sa561","key",[67,1001,260],{"class":422},[67,1003,1004],{"class":507},"len",[67,1006,186],{"class":426},[67,1008,1009],{"class":998},"reverse",[67,1011,260],{"class":422},[67,1013,1014],{"class":507},"True",[67,1016,581],{"class":426},[67,1018,1020,1023,1025,1028,1031,1034,1036,1039,1041,1044,1046,1048,1051,1053,1056,1058,1060],{"class":69,"line":1019},14,[67,1021,1022],{"class":426},"        text ",[67,1024,260],{"class":422},[67,1026,1027],{"class":426}," re.sub(",[67,1029,1030],{"class":422},"rf",[67,1032,1033],{"class":77},"'(?\u003C![\\w.!\"$])\\b",[67,1035,597],{"class":596},[67,1037,1038],{"class":426},"re.escape(name)",[67,1040,603],{"class":596},[67,1042,1043],{"class":77},"\\b(?![\\w(!])'",[67,1045,186],{"class":426},[67,1047,590],{"class":422},[67,1049,1050],{"class":77},"\"_xlpm.",[67,1052,597],{"class":596},[67,1054,1055],{"class":426},"name",[67,1057,603],{"class":596},[67,1059,606],{"class":77},[67,1061,1062],{"class":426},", text)\n",[67,1064,1066,1068,1071,1073,1076],{"class":69,"line":1065},15,[67,1067,824],{"class":422},[67,1069,1070],{"class":426}," fn ",[67,1072,501],{"class":422},[67,1074,1075],{"class":507}," NEW_FUNCS",[67,1077,1078],{"class":426},":\n",[67,1080,1082,1084,1086,1088,1090,1093,1095,1098,1100,1103,1105,1107,1110,1112,1114,1116,1119],{"class":69,"line":1081},16,[67,1083,1022],{"class":426},[67,1085,260],{"class":422},[67,1087,1027],{"class":426},[67,1089,1030],{"class":422},[67,1091,1092],{"class":77},"\"(?\u003C![\\w.])",[67,1094,597],{"class":596},[67,1096,1097],{"class":426},"fn",[67,1099,603],{"class":596},[67,1101,1102],{"class":77},"\\(\"",[67,1104,186],{"class":426},[67,1106,590],{"class":422},[67,1108,1109],{"class":77},"\"_xlfn.",[67,1111,597],{"class":596},[67,1113,1097],{"class":426},[67,1115,603],{"class":596},[67,1117,1118],{"class":77},"(\"",[67,1120,1062],{"class":426},[67,1122,1124,1127],{"class":69,"line":1123},17,[67,1125,1126],{"class":422},"    return",[67,1128,1129],{"class":426}," text\n",[67,1131,1133],{"class":69,"line":1132},18,[67,1134,440],{"emptyLinePlaceholder":439},[67,1136,1138,1140,1143,1146],{"class":69,"line":1137},19,[67,1139,662],{"class":507},[67,1141,1142],{"class":426},"(to_stored(",[67,1144,1145],{"class":77},"\"=LET(net, A2*(1-B2), net + net*C2)\"",[67,1147,1148],{"class":426},"))\n",[67,1150,1152,1154,1156,1159],{"class":69,"line":1151},20,[67,1153,662],{"class":507},[67,1155,1142],{"class":426},[67,1157,1158],{"class":77},"\"=LET(rate, C2, gross, A2*(1+rate), ROUND(gross, 2))\"",[67,1160,1148],{"class":426},[67,1162,1164,1166,1168,1171],{"class":69,"line":1163},21,[67,1165,662],{"class":507},[67,1167,1142],{"class":426},[67,1169,1170],{"class":77},"\"=LAMBDA(x, rate, x\u002F(1-rate))(A2, B2)\"",[67,1172,1148],{"class":426},[10,1174,1175,1177,1178,1180],{},[13,1176,15],{}," declares names in pairs — name, value, name, value, … result — so the names are every other argument except the last; ",[13,1179,27],{}," declares all its arguments except the last as parameters. The helper deliberately handles only simple bodies without nested parentheses in the declaration list, which covers the formulas most report scripts write. Anything more elaborate is easier to maintain written out in stored form, or, better, calculated in pandas and written as values.",[197,1182,206,1187,206,1190,206,1193,206,1196,206,1199,206,1201,206,1203,206,1205,206,1270,206,1272,206,1275,206,1277,206,1303,206,1307,206,1310,206,1312,206,1382,206,1387,206,1390,206,1393,206,1397,206,1401,206,1406,206,1409,206,1411,206,1415,206,1418,206,1421,206,1424,206,1426,206,1430],{"viewBox":1183,"role":200,"ariaLabelledBy":1184,"xmlns":204,"style":205},"4 0 752 185",[1185,1186],"let-helper-t","let-helper-d",[208,1188,1189],{"id":1185},"The three substitutions the helper makes",[212,1191,1192],{"id":1186},"A code panel showing the readable LET formula, the parameter names found, and the stored form, with callouts for the name discovery, the parameter prefix and the function prefix.",[216,1194],{"x":218,"y":219,"width":220,"height":1195,"fill":222},"185",[216,1197],{"x":225,"y":226,"width":227,"height":1198,"rx":229,"fill":230,"stroke":231,"style":232},"87.0",[234,1200,239],{"x":236,"y":237,"style":238},[241,1202],{"cx":243,"cy":244,"r":245,"fill":246,"stroke":247,"style":248},[234,1204,239],{"x":243,"y":237,"style":251},[234,1206,1207,1209,1211,1213,1216,1218,1220,1223,1225,1227,1230,1232,1234,1236,1238,1240,1242,1245,1247,1249,1251,1253,1256,1258,1260,1262,1264,1266,1268],{"x":254,"y":237,"style":255},[257,1208,260],{"style":259},[257,1210,15],{"style":259},[257,1212,265],{"style":259},[257,1214,1215],{"style":259},"rate",[257,1217,270],{"style":259},[257,1219,273],{"style":259},[257,1221,1222],{"style":259},"C2",[257,1224,270],{"style":259},[257,1226,273],{"style":259},[257,1228,1229],{"style":259},"gross",[257,1231,270],{"style":259},[257,1233,273],{"style":259},[257,1235,276],{"style":259},[257,1237,279],{"style":259},[257,1239,265],{"style":259},[257,1241,239],{"style":284},[257,1243,1244],{"style":259},"+",[257,1246,1215],{"style":259},[257,1248,293],{"style":259},[257,1250,270],{"style":259},[257,1252,273],{"style":259},[257,1254,1255],{"style":259},"ROUND",[257,1257,265],{"style":259},[257,1259,1229],{"style":259},[257,1261,270],{"style":259},[257,1263,273],{"style":259},[257,1265,304],{"style":284},[257,1267,293],{"style":259},[257,1269,293],{"style":259},[234,1271,304],{"x":236,"y":317,"style":238},[241,1273],{"cx":243,"cy":320,"r":245,"fill":1274,"stroke":247,"style":248},"#4338ca",[234,1276,304],{"x":243,"y":317,"style":251},[234,1278,1279,1282,1284,1286,1288,1290,1294,1296,1298,1301],{"x":254,"y":317,"style":255},[257,1280,1281],{"style":259},"params",[257,1283,273],{"style":259},[257,1285,260],{"style":259},[257,1287,273],{"style":259},[257,1289,597],{"style":259},[257,1291,1293],{"style":1292},"fill:var(--teal-ink,#0b6157);font-weight:400","\"rate\"",[257,1295,270],{"style":259},[257,1297,273],{"style":259},[257,1299,1300],{"style":1292},"\"gross\"",[257,1302,603],{"style":259},[234,1304,1306],{"x":236,"y":1305,"style":238},"89.0","3",[241,1308],{"cx":243,"cy":1309,"r":245,"fill":321,"stroke":247,"style":248},"84.5",[234,1311,1306],{"x":243,"y":1305,"style":251},[234,1313,1314,1316,1318,1320,1322,1324,1326,1328,1330,1332,1334,1336,1338,1340,1342,1344,1346,1348,1350,1352,1354,1356,1358,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378,1380],{"x":254,"y":1305,"style":255},[257,1315,260],{"style":259},[257,1317,330],{"style":259},[257,1319,195],{"style":259},[257,1321,15],{"style":259},[257,1323,265],{"style":259},[257,1325,339],{"style":259},[257,1327,195],{"style":259},[257,1329,1215],{"style":259},[257,1331,270],{"style":259},[257,1333,1222],{"style":259},[257,1335,270],{"style":259},[257,1337,339],{"style":259},[257,1339,195],{"style":259},[257,1341,1229],{"style":259},[257,1343,270],{"style":259},[257,1345,276],{"style":259},[257,1347,279],{"style":259},[257,1349,265],{"style":259},[257,1351,239],{"style":284},[257,1353,1244],{"style":259},[257,1355,339],{"style":259},[257,1357,195],{"style":259},[257,1359,1215],{"style":259},[257,1361,293],{"style":259},[257,1363,270],{"style":259},[257,1365,1255],{"style":259},[257,1367,265],{"style":259},[257,1369,339],{"style":259},[257,1371,195],{"style":259},[257,1373,1229],{"style":259},[257,1375,270],{"style":259},[257,1377,304],{"style":284},[257,1379,293],{"style":259},[257,1381,293],{"style":259},[216,1383],{"x":225,"y":1384,"width":1385,"height":1386,"rx":379,"fill":380,"stroke":231,"style":232},"117.0","230.7","52.0",[241,1388],{"cx":383,"cy":1389,"r":245,"fill":246,"stroke":247,"style":248},"143.0",[234,1391,239],{"x":383,"y":1392,"style":251},"147.5",[234,1394,1396],{"x":236,"y":1395,"style":390},"139.0","readable input: LET and its",[234,1398,1400],{"x":236,"y":1399,"style":390},"156.0","names as typed",[216,1402],{"x":1403,"y":1384,"width":1385,"height":1386,"rx":379,"fill":1404,"stroke":1405,"style":232},"264.7","#f0f4ff","var(--brand,#5b5cf0)",[241,1407],{"cx":1408,"cy":1389,"r":245,"fill":1274,"stroke":247,"style":248},"284.7",[234,1410,304],{"x":1408,"y":1392,"style":251},[234,1412,1414],{"x":1413,"y":1395,"style":390},"304.7","names = every other LET",[234,1416,1417],{"x":1413,"y":1399,"style":390},"argument except the last",[216,1419],{"x":1420,"y":1384,"width":1385,"height":1386,"rx":379,"fill":313,"stroke":314,"style":232},"509.3",[241,1422],{"cx":1423,"cy":1389,"r":245,"fill":321,"stroke":247,"style":248},"529.3",[234,1425,1306],{"x":1423,"y":1392,"style":251},[234,1427,1429],{"x":1428,"y":1395,"style":390},"549.3","_xlfn. on LET, _xlpm. on each",[234,1431,1432],{"x":1428,"y":1399,"style":390},"name; ROUND stays plain",[53,1434,1436],{"id":1435},"step-3-check-the-result-without-excel","Step 3 — Check the result without Excel",[10,1438,1439,1440,1442],{},"LibreOffice evaluates ",[13,1441,15],{},", so it can recalculate a copy of the file and prove the formulas work. Write the formulas through the helper, convert with headless LibreOffice, and read the calculated values back:",[58,1444,1446],{"className":413,"code":1445,"language":415,"meta":63,"style":63},"wb = Workbook()\nws = wb.active\nws.append([\"List price\", \"Discount\", \"Tax rate\", \"Gross\"])\nfor row in [[100, 0.2, 0.25], [80, 0.1, 0.25], [250, 0.0, 0.1]]:\n    ws.append(row)\nfor r in range(2, 5):\n    ws[f\"D{r}\"] = to_stored(f\"=LET(net, A{r}*(1-B{r}), net + net*C{r})\")\nws[\"F1\"] = \"=LET(x, 2, x*21)\"          # no prefixes: what goes wrong\nwb.save(\"let_check.xlsx\")\n",[13,1447,1448,1456,1464,1484,1530,1534,1554,1610,1629],{"__ignoreMap":63},[67,1449,1450,1452,1454],{"class":69,"line":70},[67,1451,446],{"class":426},[67,1453,260],{"class":422},[67,1455,451],{"class":426},[67,1457,1458,1460,1462],{"class":69,"line":436},[67,1459,457],{"class":426},[67,1461,260],{"class":422},[67,1463,462],{"class":426},[67,1465,1466,1468,1470,1472,1474,1476,1478,1480,1482],{"class":69,"line":443},[67,1467,468],{"class":426},[67,1469,471],{"class":77},[67,1471,186],{"class":426},[67,1473,476],{"class":77},[67,1475,186],{"class":426},[67,1477,481],{"class":77},[67,1479,186],{"class":426},[67,1481,486],{"class":77},[67,1483,489],{"class":426},[67,1485,1486,1488,1490,1492,1494,1496,1498,1500,1502,1504,1506,1508,1510,1512,1514,1516,1518,1520,1522,1524,1526,1528],{"class":69,"line":454},[67,1487,495],{"class":422},[67,1489,498],{"class":426},[67,1491,501],{"class":422},[67,1493,504],{"class":426},[67,1495,508],{"class":507},[67,1497,186],{"class":426},[67,1499,513],{"class":507},[67,1501,186],{"class":426},[67,1503,518],{"class":507},[67,1505,521],{"class":426},[67,1507,524],{"class":507},[67,1509,186],{"class":426},[67,1511,529],{"class":507},[67,1513,186],{"class":426},[67,1515,518],{"class":507},[67,1517,521],{"class":426},[67,1519,538],{"class":507},[67,1521,186],{"class":426},[67,1523,543],{"class":507},[67,1525,186],{"class":426},[67,1527,529],{"class":507},[67,1529,550],{"class":426},[67,1531,1532],{"class":69,"line":465},[67,1533,556],{"class":426},[67,1535,1536,1538,1540,1542,1544,1546,1548,1550,1552],{"class":69,"line":492},[67,1537,495],{"class":422},[67,1539,564],{"class":426},[67,1541,501],{"class":422},[67,1543,569],{"class":507},[67,1545,265],{"class":426},[67,1547,304],{"class":507},[67,1549,186],{"class":426},[67,1551,578],{"class":507},[67,1553,581],{"class":426},[67,1555,1556,1558,1560,1562,1564,1566,1568,1570,1572,1574,1577,1579,1582,1584,1586,1588,1590,1592,1594,1596,1599,1601,1603,1605,1608],{"class":69,"line":553},[67,1557,587],{"class":426},[67,1559,590],{"class":422},[67,1561,593],{"class":77},[67,1563,597],{"class":596},[67,1565,600],{"class":426},[67,1567,603],{"class":596},[67,1569,606],{"class":77},[67,1571,609],{"class":426},[67,1573,260],{"class":422},[67,1575,1576],{"class":426}," to_stored(",[67,1578,590],{"class":422},[67,1580,1581],{"class":77},"\"=LET(net, A",[67,1583,597],{"class":596},[67,1585,600],{"class":426},[67,1587,603],{"class":596},[67,1589,626],{"class":77},[67,1591,597],{"class":596},[67,1593,600],{"class":426},[67,1595,603],{"class":596},[67,1597,1598],{"class":77},"), net + net*C",[67,1600,597],{"class":596},[67,1602,600],{"class":426},[67,1604,603],{"class":596},[67,1606,1607],{"class":77},")\"",[67,1609,656],{"class":426},[67,1611,1612,1615,1618,1620,1622,1625],{"class":69,"line":559},[67,1613,1614],{"class":426},"ws[",[67,1616,1617],{"class":77},"\"F1\"",[67,1619,609],{"class":426},[67,1621,260],{"class":422},[67,1623,1624],{"class":77}," \"=LET(x, 2, x*21)\"",[67,1626,1628],{"class":1627},"s-wDw","          # no prefixes: what goes wrong\n",[67,1630,1631,1633,1636],{"class":69,"line":584},[67,1632,650],{"class":426},[67,1634,1635],{"class":77},"\"let_check.xlsx\"",[67,1637,656],{"class":426},[58,1639,1641],{"className":60,"code":1640,"language":62,"meta":63,"style":63},"soffice -env:UserInstallation=file:\u002F\u002F$PWD\u002Flo_profile --headless --convert-to xlsx --outdir recalculated let_check.xlsx > \u002Fdev\u002Fnull 2>&1\nls recalculated\n",[13,1642,1643,1684],{"__ignoreMap":63},[67,1644,1645,1648,1651,1654,1657,1660,1663,1666,1669,1672,1675,1678,1681],{"class":69,"line":70},[67,1646,1647],{"class":73},"soffice",[67,1649,1650],{"class":507}," -env:UserInstallation=file:\u002F\u002F",[67,1652,1653],{"class":426},"$PWD",[67,1655,1656],{"class":507},"\u002Flo_profile",[67,1658,1659],{"class":507}," --headless",[67,1661,1662],{"class":507}," --convert-to",[67,1664,1665],{"class":77}," xlsx",[67,1667,1668],{"class":507}," --outdir",[67,1670,1671],{"class":77}," recalculated",[67,1673,1674],{"class":77}," let_check.xlsx",[67,1676,1677],{"class":422}," >",[67,1679,1680],{"class":77}," \u002Fdev\u002Fnull",[67,1682,1683],{"class":422}," 2>&1\n",[67,1685,1686,1689],{"class":69,"line":436},[67,1687,1688],{"class":73},"ls",[67,1690,1691],{"class":77}," recalculated\n",[58,1693,1695],{"className":413,"code":1694,"language":415,"meta":63,"style":63},"from openpyxl import load_workbook\n\nws = load_workbook(\"recalculated\u002Flet_check.xlsx\", data_only=True).active\nprint([ws[f\"D{r}\"].value for r in range(2, 5)], ws[\"F1\"].value)\n",[13,1696,1697,1708,1712,1736],{"__ignoreMap":63},[67,1698,1699,1701,1703,1705],{"class":69,"line":70},[67,1700,423],{"class":422},[67,1702,427],{"class":426},[67,1704,430],{"class":422},[67,1706,1707],{"class":426}," load_workbook\n",[67,1709,1710],{"class":69,"line":436},[67,1711,440],{"emptyLinePlaceholder":439},[67,1713,1714,1716,1718,1721,1724,1726,1729,1731,1733],{"class":69,"line":443},[67,1715,457],{"class":426},[67,1717,260],{"class":422},[67,1719,1720],{"class":426}," load_workbook(",[67,1722,1723],{"class":77},"\"recalculated\u002Flet_check.xlsx\"",[67,1725,186],{"class":426},[67,1727,1728],{"class":998},"data_only",[67,1730,260],{"class":422},[67,1732,1014],{"class":507},[67,1734,1735],{"class":426},").active\n",[67,1737,1738,1740,1743,1745,1747,1749,1751,1753,1755,1758,1760,1762,1764,1766,1768,1770,1772,1774,1777,1779],{"class":69,"line":454},[67,1739,662],{"class":507},[67,1741,1742],{"class":426},"([ws[",[67,1744,590],{"class":422},[67,1746,593],{"class":77},[67,1748,597],{"class":596},[67,1750,600],{"class":426},[67,1752,603],{"class":596},[67,1754,606],{"class":77},[67,1756,1757],{"class":426},"].value ",[67,1759,495],{"class":422},[67,1761,564],{"class":426},[67,1763,501],{"class":422},[67,1765,569],{"class":507},[67,1767,265],{"class":426},[67,1769,304],{"class":507},[67,1771,186],{"class":426},[67,1773,578],{"class":507},[67,1775,1776],{"class":426},")], ws[",[67,1778,1617],{"class":77},[67,1780,671],{"class":426},[10,1782,1783,1784,1787,1788,1790,1791,1795],{},"The three gross prices come back as 100, 90 and 275 — net price plus tax — while the unprefixed formula in ",[13,1785,1786],{},"F1"," is ",[13,1789,39],{},", the same result Excel gives. This check is worth keeping in a test suite; ",[47,1792,1794],{"href":1793},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Frecalculate-excel-formulas-without-excel-in-python\u002F","Recalculate Excel Formulas Without Excel"," shows how to wrap it.",[53,1797,1799],{"id":1798},"step-4-named-lambda-functions","Step 4 — Named LAMBDA functions",[10,1801,1802,1803,1806,1807,905],{},"A LAMBDA becomes truly useful when it is stored as a defined name: the workbook gains a function, ",[13,1804,1805],{},"GrossUp",", that any cell can call. The definition is a defined name whose value is the stored LAMBDA; the calls are ordinary formulas. xlsxwriter supports this directly, and writes the calls as dynamic array formulas so Excel does not insert an implicit-intersection ",[13,1808,1809],{},"@",[58,1811,1813],{"className":413,"code":1812,"language":415,"meta":63,"style":63},"import xlsxwriter\n\nbook = xlsxwriter.Workbook(\"grossup.xlsx\")\nsheet = book.add_worksheet(\"Pay\")\nsheet.write_row(\"A1\", [\"Net pay\", \"Tax rate\", \"Gross needed\"])\nfor i, (net, rate) in enumerate([(3000, 0.2), (4200, 0.3), (1800, 0.0)], start=2):\n    sheet.write_row(f\"A{i}\", [net, rate])\n    sheet.write_dynamic_array_formula(f\"C{i}\", f\"=GrossUp(A{i},B{i})\")\nbook.define_name(\"GrossUp\", \"=\" + to_stored(\"=LAMBDA(net, rate, net\u002F(1-rate))\")[1:])\nbook.close()\n\nimport zipfile\nxml = zipfile.ZipFile(\"grossup.xlsx\").read(\"xl\u002Fworkbook.xml\").decode()\nprint(re.search(r\"\u003CdefinedName.*?\u003C\u002FdefinedName>\", xml).group(0))\n",[13,1814,1815,1821,1825,1840,1855,1880,1934,1956,2000,2029,2034,2038,2045,2066],{"__ignoreMap":63},[67,1816,1817,1819],{"class":69,"line":70},[67,1818,430],{"class":422},[67,1820,84],{"class":426},[67,1822,1823],{"class":69,"line":436},[67,1824,440],{"emptyLinePlaceholder":439},[67,1826,1827,1830,1832,1835,1838],{"class":69,"line":443},[67,1828,1829],{"class":426},"book ",[67,1831,260],{"class":422},[67,1833,1834],{"class":426}," xlsxwriter.Workbook(",[67,1836,1837],{"class":77},"\"grossup.xlsx\"",[67,1839,656],{"class":426},[67,1841,1842,1845,1847,1850,1853],{"class":69,"line":454},[67,1843,1844],{"class":426},"sheet ",[67,1846,260],{"class":422},[67,1848,1849],{"class":426}," book.add_worksheet(",[67,1851,1852],{"class":77},"\"Pay\"",[67,1854,656],{"class":426},[67,1856,1857,1860,1863,1866,1869,1871,1873,1875,1878],{"class":69,"line":465},[67,1858,1859],{"class":426},"sheet.write_row(",[67,1861,1862],{"class":77},"\"A1\"",[67,1864,1865],{"class":426},", [",[67,1867,1868],{"class":77},"\"Net pay\"",[67,1870,186],{"class":426},[67,1872,481],{"class":77},[67,1874,186],{"class":426},[67,1876,1877],{"class":77},"\"Gross needed\"",[67,1879,489],{"class":426},[67,1881,1882,1884,1887,1889,1892,1895,1898,1900,1902,1905,1908,1910,1913,1915,1918,1920,1922,1925,1928,1930,1932],{"class":69,"line":492},[67,1883,495],{"class":422},[67,1885,1886],{"class":426}," i, (net, rate) ",[67,1888,501],{"class":422},[67,1890,1891],{"class":507}," enumerate",[67,1893,1894],{"class":426},"([(",[67,1896,1897],{"class":507},"3000",[67,1899,186],{"class":426},[67,1901,513],{"class":507},[67,1903,1904],{"class":426},"), (",[67,1906,1907],{"class":507},"4200",[67,1909,186],{"class":426},[67,1911,1912],{"class":507},"0.3",[67,1914,1904],{"class":426},[67,1916,1917],{"class":507},"1800",[67,1919,186],{"class":426},[67,1921,543],{"class":507},[67,1923,1924],{"class":426},")], ",[67,1926,1927],{"class":998},"start",[67,1929,260],{"class":422},[67,1931,304],{"class":507},[67,1933,581],{"class":426},[67,1935,1936,1939,1941,1944,1946,1949,1951,1953],{"class":69,"line":553},[67,1937,1938],{"class":426},"    sheet.write_row(",[67,1940,590],{"class":422},[67,1942,1943],{"class":77},"\"A",[67,1945,597],{"class":596},[67,1947,1948],{"class":426},"i",[67,1950,603],{"class":596},[67,1952,606],{"class":77},[67,1954,1955],{"class":426},", [net, rate])\n",[67,1957,1958,1961,1963,1966,1968,1970,1972,1974,1976,1978,1981,1983,1985,1987,1990,1992,1994,1996,1998],{"class":69,"line":559},[67,1959,1960],{"class":426},"    sheet.write_dynamic_array_formula(",[67,1962,590],{"class":422},[67,1964,1965],{"class":77},"\"C",[67,1967,597],{"class":596},[67,1969,1948],{"class":426},[67,1971,603],{"class":596},[67,1973,606],{"class":77},[67,1975,186],{"class":426},[67,1977,590],{"class":422},[67,1979,1980],{"class":77},"\"=GrossUp(A",[67,1982,597],{"class":596},[67,1984,1948],{"class":426},[67,1986,603],{"class":596},[67,1988,1989],{"class":77},",B",[67,1991,597],{"class":596},[67,1993,1948],{"class":426},[67,1995,603],{"class":596},[67,1997,1607],{"class":77},[67,1999,656],{"class":426},[67,2001,2002,2005,2008,2010,2013,2016,2018,2021,2024,2026],{"class":69,"line":584},[67,2003,2004],{"class":426},"book.define_name(",[67,2006,2007],{"class":77},"\"GrossUp\"",[67,2009,186],{"class":426},[67,2011,2012],{"class":77},"\"=\"",[67,2014,2015],{"class":422}," +",[67,2017,1576],{"class":426},[67,2019,2020],{"class":77},"\"=LAMBDA(net, rate, net\u002F(1-rate))\"",[67,2022,2023],{"class":426},")[",[67,2025,239],{"class":507},[67,2027,2028],{"class":426},":])\n",[67,2030,2031],{"class":69,"line":647},[67,2032,2033],{"class":426},"book.close()\n",[67,2035,2036],{"class":69,"line":659},[67,2037,440],{"emptyLinePlaceholder":439},[67,2039,2040,2042],{"class":69,"line":954},[67,2041,430],{"class":422},[67,2043,2044],{"class":426}," zipfile\n",[67,2046,2047,2050,2052,2055,2057,2060,2063],{"class":69,"line":982},[67,2048,2049],{"class":426},"xml ",[67,2051,260],{"class":422},[67,2053,2054],{"class":426}," zipfile.ZipFile(",[67,2056,1837],{"class":77},[67,2058,2059],{"class":426},").read(",[67,2061,2062],{"class":77},"\"xl\u002Fworkbook.xml\"",[67,2064,2065],{"class":426},").decode()\n",[67,2067,2068,2070,2073,2075,2078,2080,2083,2086,2089,2091],{"class":69,"line":1019},[67,2069,662],{"class":507},[67,2071,2072],{"class":426},"(re.search(",[67,2074,600],{"class":422},[67,2076,2077],{"class":77},"\"\u003CdefinedName",[67,2079,195],{"class":507},[67,2081,2082],{"class":422},"*?",[67,2084,2085],{"class":77},"\u003C\u002FdefinedName>\"",[67,2087,2088],{"class":426},", xml).group(",[67,2090,219],{"class":507},[67,2092,1148],{"class":426},[10,2094,2095,2096,2098,2099,2101,2102,2105],{},"In Excel 365, ",[13,2097,1805],{}," then appears in the Name Manager and in formula autocomplete like a built-in function, and changing its definition updates every cell that calls it. LibreOffice cannot evaluate ",[13,2100,27],{},", so this part has to be checked in Excel itself before the workbook goes out. openpyxl can write the same defined name — ",[13,2103,2104],{},"wb.defined_names[\"GrossUp\"] = DefinedName(\"GrossUp\", attr_text=...)"," — but its cell formulas are written as ordinary formulas, which is fine for single results.",[197,2107,206,2112,206,2115,206,2118,206,2121,206,2127,206,2137,206,2142,206,2147,206,2152,206,2159,206,2165,206,2170,206,2173,206,2178,206,2181,206,2186,206,2189,206,2193,206,2196,206,2199,206,2202,206,2207,206,2211,206,2215,206,2218],{"viewBox":2108,"role":200,"ariaLabelledBy":2109,"xmlns":204,"style":205},"4 0 752 332",[2110,2111],"let-named-t","let-named-d",[208,2113,2114],{"id":2110},"One named LAMBDA, many callers",[212,2116,2117],{"id":2111},"A defined name GrossUp holding a LAMBDA fans out to three cells that call it with different arguments.",[216,2119],{"x":218,"y":219,"width":220,"height":2120,"fill":222},"332",[216,2122],{"x":225,"y":2123,"width":2124,"height":2125,"rx":229,"fill":1404,"stroke":1405,"style":2126},"119.5","220.0","93.0","stroke-width:2px",[2128,2129,2131],"g",{"transform":2130},"translate(118.0,132.5) scale(1.000)",[2132,2133],"path",{"d":2134,"fill":247,"stroke":2135,"style":2136},"M9 8l-4 4 4 4 M15 8l4 4-4 4","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[234,2138,1805],{"x":2139,"y":2140,"style":2141},"130.0","179.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[234,2143,2146],{"x":2139,"y":2144,"style":2145},"198.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","defined name: _xlfn.LAMBDA(...)",[216,2148],{"x":2149,"y":226,"width":2150,"height":2151,"rx":229,"fill":313,"stroke":314,"style":2126},"490.0","250.0","92.0",[2128,2153,2155],{"transform":2154},"translate(603.0,29.0) scale(1.000)",[2132,2156],{"d":2157,"fill":247,"stroke":2158,"style":2136},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--teal-ink,#0b6157)",[234,2160,2164],{"x":2161,"y":2162,"style":2163},"615.0","75.0","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","C2 =GrossUp(A2,B2)",[234,2166,2169],{"x":2161,"y":2167,"style":2168},"94.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","3000 at 20% → 3750",[2132,2171],{"d":2172,"fill":247,"stroke":1405,"style":2126},"M244.0,166.0 C364.0,166.0 364.0,62.0 480.0,62.0",[2174,2175],"polygon",{"points":2176,"fill":2177},"484.0,62.0 475.0,67.0 475.0,57.0","#5b5cf0",[216,2179],{"x":2149,"y":2180,"width":2150,"height":2151,"rx":229,"fill":313,"stroke":314,"style":2126},"120.0",[2128,2182,2184],{"transform":2183},"translate(603.0,133.0) scale(1.000)",[2132,2185],{"d":2157,"fill":247,"stroke":2158,"style":2136},[234,2187,2188],{"x":2161,"y":2140,"style":2163},"C3 =GrossUp(A3,B3)",[234,2190,2192],{"x":2161,"y":2191,"style":2168},"198.0","4200 at 30% → 6000",[2132,2194],{"d":2195,"fill":247,"stroke":1405,"style":2126},"M244.0,166.0 C364.0,166.0 364.0,166.0 480.0,166.0",[2174,2197],{"points":2198,"fill":2177},"484.0,166.0 475.0,171.0 475.0,161.0",[216,2200],{"x":2149,"y":2201,"width":2150,"height":2151,"rx":229,"fill":313,"stroke":314,"style":2126},"224.0",[2128,2203,2205],{"transform":2204},"translate(603.0,237.0) scale(1.000)",[2132,2206],{"d":2157,"fill":247,"stroke":2158,"style":2136},[234,2208,2210],{"x":2161,"y":2209,"style":2163},"283.0","C4 =GrossUp(A4,B4)",[234,2212,2214],{"x":2161,"y":2213,"style":2168},"302.0","1800 at 0% → 1800",[2132,2216],{"d":2217,"fill":247,"stroke":1405,"style":2126},"M244.0,166.0 C364.0,166.0 364.0,270.0 480.0,270.0",[2174,2219],{"points":2220,"fill":2177},"484.0,270.0 475.0,275.0 475.0,265.0",[53,2222,2224],{"id":2223},"xlsxwriters-use_future_functions-option","xlsxwriter's use_future_functions option",[10,2226,2227,2228,2231,2232,2235,2236,2238,2239,2241,2242,2244,2245,2247,2248,2251],{},"xlsxwriter can add the ",[13,2229,2230],{},"_xlfn."," prefixes itself: ",[13,2233,2234],{},"xlsxwriter.Workbook(path, {\"use_future_functions\": True})",". It does not add ",[13,2237,687],{}," to ",[13,2240,15],{}," or ",[13,2243,27],{}," parameters, though — a ",[13,2246,15],{}," written through it is stored as ",[13,2249,2250],{},"_xlfn.LET(net,...)"," with bare names. Keep using a helper, or the stored form, for the parameters. The option also has a cost: xlsxwriter scans every formula with a list of function names, which slows down very formula-heavy workbooks slightly.",[53,2253,2255],{"id":2254},"common-pitfalls","Common pitfalls",[2257,2258,2259,2273,2292,2298,2312],"ul",{},[2260,2261,2262,273,2266,2269,2270,2272],"li",{},[2263,2264,2265],"strong",{},"Prefix on the function but not the parameters.",[13,2267,2268],{},"_xlfn.LET(net, ...)"," still leaves ",[13,2271,23],{}," undefined as far as Excel is concerned.",[2260,2274,2275,2278,2279,186,2282,2241,2285,2288,2289,195],{},[2263,2276,2277],{},"A parameter that looks like a cell reference."," Names such as ",[13,2280,2281],{},"A1",[13,2283,2284],{},"R2C3",[13,2286,2287],{},"TAX2026"," collide with references in some reference styles. Use descriptive names with underscores: ",[13,2290,2291],{},"tax_rate",[2260,2293,2294,2297],{},[2263,2295,2296],{},"Reading the formula back."," openpyxl returns the stored text with prefixes; strip them if you show formulas to people.",[2260,2299,2300,2303,2304,2306,2307,111,2309,2311],{},[2263,2301,2302],{},"Sharing with older Excel."," Excel 2019 and earlier show ",[13,2305,39],{}," for ",[13,2308,15],{},[13,2310,27],{}," no matter how they are stored. If the audience is mixed, calculate in Python and write values.",[2260,2313,2314,2317,2318,195],{},[2263,2315,2316],{},"Cached values."," Neither library calculates formulas, so the file has no stored results until Excel or LibreOffice opens it — see ",[47,2319,2321],{"href":2320},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fstore-formula-results-with-xlsxwriter\u002F","Store Formula Results with xlsxwriter",[53,2323,2325],{"id":2324},"conclusion","Conclusion",[10,2327,2328,2329,111,2331,2333,2334,111,2337,2340,2341,2344,2345,2347],{},"Excel stores ",[13,2330,15],{},[13,2332,27],{}," as ",[13,2335,2336],{},"_xlfn.LET",[13,2338,2339],{},"_xlfn.LAMBDA",", and every name they declare as ",[13,2342,2343],{},"_xlpm.name",", at the declaration and at each use. Write that stored form from openpyxl or xlsxwriter — generated by a helper rather than typed — check ",[13,2346,15],{}," workbooks by letting LibreOffice recalculate a copy, and define named LAMBDA functions as defined names whose calls are written as ordinary or dynamic array formulas. Check LAMBDA workbooks in Excel 365 itself.",[53,2349,2351],{"id":2350},"frequently-asked-questions","Frequently asked questions",[10,2353,2354,2357],{},[2263,2355,2356],{},"Why does my LET formula show #NAME? in Excel?","\nThe file must contain _xlfn.LET and _xlpm. before each parameter name, which is how Excel stores these newer functions. openpyxl and xlsxwriter write your text exactly as given, so a formula typed as =LET(x, 1, x+1) is stored without the prefixes and Excel does not recognise it.",[10,2359,2360,2363],{},[2263,2361,2362],{},"What is the _xlpm prefix?","\nIt marks a name as a parameter declared inside LET or LAMBDA, as opposed to a defined name or a cell reference. Excel adds it when it saves the file and hides it when it shows the formula.",[10,2365,2366,2369],{},[2263,2367,2368],{},"Can openpyxl create a named LAMBDA function?","\nYes. Add a defined name whose value is the stored LAMBDA, _xlfn.LAMBDA(_xlpm.x,...), and write cell formulas that call the name. xlsxwriter's define_name does the same and can write the calls as dynamic array formulas.",[10,2371,2372,2375],{},[2263,2373,2374],{},"Can LibreOffice calculate LET and LAMBDA?","\nRecent LibreOffice versions calculate LET, so a headless LibreOffice recalculation is a good test. LAMBDA is not supported and evaluates to an error there, so named LAMBDA workbooks must be checked in Excel 365.",[53,2377,2379],{"id":2378},"related","Related",[2257,2381,2382,2388,2395,2402],{},[2260,2383,2384,2385,2387],{},"Up one level: ",[47,2386,50],{"href":49}," — writing, reading and recalculating formulas.",[2260,2389,2390,2394],{},[47,2391,2393],{"href":2392},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-dynamic-array-formulas-with-python\u002F","Write Dynamic Array Formulas with Python"," — FILTER, SORT, UNIQUE and spill ranges.",[2260,2396,2397,2401],{},[47,2398,2400],{"href":2399},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-formula-to-excel-cell-with-openpyxl\u002F","Write a Formula to an Excel Cell with openpyxl"," — the basics this page builds on.",[2260,2403,2404,2408],{},[47,2405,2407],{"href":2406},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fcreate-named-range-in-excel-with-openpyxl\u002F","Create a Named Range in Excel with openpyxl"," — the defined-name API used for LAMBDA.",[2410,2411,2412],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}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 .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":63,"searchDepth":436,"depth":436,"links":2414},[2415,2416,2417,2418,2419,2420,2421,2422,2423,2424,2425],{"id":55,"depth":436,"text":56},{"id":104,"depth":436,"text":105},{"id":406,"depth":436,"text":407},{"id":680,"depth":436,"text":681},{"id":1435,"depth":436,"text":1436},{"id":1798,"depth":436,"text":1799},{"id":2223,"depth":436,"text":2224},{"id":2254,"depth":436,"text":2255},{"id":2324,"depth":436,"text":2325},{"id":2350,"depth":436,"text":2351},{"id":2378,"depth":436,"text":2379},"2026-09-29","LET and LAMBDA written from openpyxl or xlsxwriter show #NAME? unless they are stored the way Excel stores them: _xlfn. before the function and _xlpm. before every parameter name. A helper that builds the stored form, named LAMBDA functions as defined names, and how to check the result.","md",[2430,2432,2434,2436],{"q":2356,"a":2431},"The file must contain _xlfn.LET and _xlpm. before each parameter name, which is how Excel stores these newer functions. openpyxl and xlsxwriter write your text exactly as given, so a formula typed as =LET(x, 1, x+1) is stored without the prefixes and Excel does not recognise it.",{"q":2362,"a":2433},"It marks a name as a parameter declared inside LET or LAMBDA, as opposed to a defined name or a cell reference. Excel adds it when it saves the file and hides it when it shows the formula.",{"q":2368,"a":2435},"Yes. Add a defined name whose value is the stored LAMBDA, _xlfn.LAMBDA(_xlpm.x,...), and write cell formulas that call the name. xlsxwriter's define_name does the same and can write the calls as dynamic array formulas.",{"q":2374,"a":2437},"Recent LibreOffice versions calculate LET, so a headless LibreOffice recalculation is a good test. LAMBDA is not supported and evaluates to an error there, so named LAMBDA workbooks must be checked in Excel 365.","write let and lambda formulas from python",null,{"breadcrumb":2441},[2442,2445,2448],{"name":2443,"item":2444},"Home","\u002F",{"name":2446,"item":2447},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":50,"item":49},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-let-and-lambda-formulas-from-python",{"title":2451,"description":2452},"Write Excel LET and LAMBDA Formulas with openpyxl and xlsxwriter","Write =LET() and =LAMBDA() from Python without #NAME? errors: the _xlfn.LET and _xlpm. parameter prefixes, a helper to generate them, named LAMBDA functions via defined names, xlsxwriter use_future_functions, and checking with LibreOffice.","write-let-and-lambda-formulas-from-python","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-let-and-lambda-formulas-from-python\u002Findex",[2456,2457],"excel-formulas","openpyxl","how-to","8yO3uj4suzUIKqvjPLgVZPyjZU0VSD3LseKsBc55Uf8",[2461,2465],{"title":2462,"path":2463,"stem":2464,"children":-1},"Write Legacy Array Formulas (Ctrl+Shift+Enter) with openpyxl","\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-legacy-array-formulas-with-openpyxl","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-legacy-array-formulas-with-openpyxl\u002Findex",{"title":2466,"path":2467,"stem":2468,"children":-1},"Write Lookup Formulas (XLOOKUP, VLOOKUP, INDEX\u002FMATCH) with openpyxl","\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-lookup-formulas-with-openpyxl","getting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-lookup-formulas-with-openpyxl\u002Findex",[2470,2472],{"path":2471,"title":2446},"\u002Fgetting-started-with-python-excel-automation",{"path":2473,"title":50},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python",[],[2476,2479,2483,2486,2489,2492],{"path":2477,"title":2478,"kind":2458},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fstore-formula-results-with-xlsxwriter","Store Formula Results with xlsxwriter So Every Viewer Shows Values",{"path":2480,"title":2481,"kind":2482},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-with-formula-errors-using-conditional-formatting","Highlight Cells with Formula Errors Using Conditional Formatting","fix",{"path":2484,"title":2485,"kind":2458},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel","Goal Seek in Python Instead of Excel",{"path":2487,"title":2488,"kind":2458},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcheck-that-totals-in-a-report-add-up","Check That Totals in a Report Add Up",{"path":2490,"title":2491,"kind":2458},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fcall-excel-worksheet-functions-through-microsoft-graph","Call Excel Worksheet Functions Through Microsoft Graph",{"path":2493,"title":2494,"kind":2458},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Frecalculate-excel-online-workbooks-with-graph-sessions","Recalculate Excel Online Workbooks with Graph Sessions",1790972075981]