[{"data":1,"prerenderedAt":2698},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fiterate-over-rows-and-columns-with-openpyxl":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fiterate-over-rows-and-columns-with-openpyxl":2690},{"id":4,"title":5,"body":6,"dateModified":2664,"datePublished":2664,"description":2665,"extension":2666,"faq":2667,"meta":2681,"navigation":269,"path":2682,"seo":2683,"slug":2686,"stem":2687,"type":2688,"__hash__":2689},"docs\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fiterate-over-rows-and-columns-with-openpyxl\u002Findex.md","Iterate over Rows and Columns with openpyxl",{"type":7,"value":8,"toc":2651},"minimark",[9,32,208,213,238,241,449,453,462,566,576,665,668,678,753,756,794,807,811,816,958,968,972,986,1086,1089,1343,1350,1354,1451,1454,1785,1799,1803,1809,2030,2041,2045,2192,2196,2199,2461,2476,2501,2505,2521,2525,2537,2548,2571,2593,2608,2612,2647],[10,11,12,13,17,18,21,22,25,26,31],"p",{},"Sometimes pandas is the wrong tool. You need the cell's number format, or its fill colour, or you are writing values into a template and must not disturb anything else. That means walking the sheet yourself, and openpyxl gives you two methods — ",[14,15,16],"code",{},"iter_rows"," and ",[14,19,20],{},"iter_cols"," — plus a handful of options that make the difference between a loop that finishes in a second and one that takes a minute. This guide covers the mechanics, the ",[14,23,24],{},"max_row"," trap that catches everyone, and the patterns worth reusing. It is part of ",[27,28,30],"a",{"href":29},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002F","Using openpyxl for Excel File Manipulation",".",[33,34,43,44,43,48,43,52,43,59,43,66,43,71,43,81,43,87,43,90,43,94,43,96,43,100,43,104,43,109,43,111,43,114,43,116,43,119,43,122,43,126,43,128,43,131,43,133,43,136,43,142,43,147,43,152,43,157,43,161,43,165,43,168,43,171,43,174,43,176,43,178,43,180,43,182,43,184,43,186,43,188,43,190,43,192,43,194,43,196,43,198,43,201,43,205],"svg",{"viewBox":35,"role":36,"ariaLabel":37,"ariaLabelledBy":38,"xmlns":41,"style":42},"0 0 800 244","img","iter_rows yields tuples row by row while iter_cols yields them column by column, over the same bounded range of a sheet.",[39,40],"iter-t","iter-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:800px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[45,46,47],"title",{"id":39},"Two traversal orders over the same range",[49,50,51],"desc",{"id":40},"A three by three grid of cells. iter_rows walks it left to right then top to bottom, yielding one tuple per row: A2 B2 C2, then A3 B3 C3. iter_cols walks top to bottom then left to right, yielding one tuple per column: A2 A3 A4, then B2 B3 B4. Both accept the same min and max row and column bounds.",[53,54],"rect",{"x":55,"y":55,"width":56,"height":57,"fill":58},"0","800","244","#ffffff",[60,61,65],"text",{"x":62,"y":63,"style":64},"196","30","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","iter_rows — one tuple per row",[60,67,70],{"x":68,"y":63,"style":69},"604","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","iter_cols — one tuple per column",[53,72],{"x":73,"y":74,"width":75,"height":76,"rx":77,"fill":78,"stroke":79,"style":80},"88","46","72","40","6","#ebebfd","var(--brand,#5b5cf0)","stroke-width:2px",[60,82,86],{"x":83,"y":84,"style":85},"124","71","font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","A2",[53,88],{"x":89,"y":74,"width":75,"height":76,"rx":77,"fill":78,"stroke":79,"style":80},"166",[60,91,93],{"x":92,"y":84,"style":85},"202","B2",[53,95],{"x":57,"y":74,"width":75,"height":76,"rx":77,"fill":78,"stroke":79,"style":80},[60,97,99],{"x":98,"y":84,"style":85},"280","C2",[53,101],{"x":73,"y":102,"width":75,"height":76,"rx":77,"fill":103,"stroke":79},"92","#f0f4ff",[60,105,108],{"x":83,"y":106,"style":107},"117","font-size:11px;fill:var(--text,#172033);text-anchor:middle","A3",[53,110],{"x":89,"y":102,"width":75,"height":76,"rx":77,"fill":103,"stroke":79},[60,112,113],{"x":92,"y":106,"style":107},"B3",[53,115],{"x":57,"y":102,"width":75,"height":76,"rx":77,"fill":103,"stroke":79},[60,117,118],{"x":98,"y":106,"style":107},"C3",[53,120],{"x":73,"y":121,"width":75,"height":76,"rx":77,"fill":103,"stroke":79},"138",[60,123,125],{"x":83,"y":124,"style":107},"163","A4",[53,127],{"x":89,"y":121,"width":75,"height":76,"rx":77,"fill":103,"stroke":79},[60,129,130],{"x":92,"y":124,"style":107},"B4",[53,132],{"x":57,"y":121,"width":75,"height":76,"rx":77,"fill":103,"stroke":79},[60,134,135],{"x":98,"y":124,"style":107},"C4",[137,138],"path",{"d":139,"fill":140,"stroke":79,"style":141},"M96 66 H 308","none","stroke-width:2.5px",[143,144],"polygon",{"points":145,"fill":146},"316,66 304,60 304,72","#5b5cf0",[60,148,151],{"x":92,"y":149,"style":150},"204","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","yields (A2,B2,C2) then (A3,B3,C3)",[53,153],{"x":154,"y":74,"width":75,"height":76,"rx":77,"fill":155,"stroke":156,"style":80},"496","#d9f4f1","var(--teal,#0f9488)",[60,158,86],{"x":159,"y":84,"style":160},"532","font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[53,162],{"x":163,"y":74,"width":75,"height":76,"rx":77,"fill":164,"stroke":156},"574","#ecf6f5",[60,166,93],{"x":167,"y":84,"style":107},"610",[53,169],{"x":170,"y":74,"width":75,"height":76,"rx":77,"fill":164,"stroke":156},"652",[60,172,99],{"x":173,"y":84,"style":107},"688",[53,175],{"x":154,"y":102,"width":75,"height":76,"rx":77,"fill":155,"stroke":156,"style":80},[60,177,108],{"x":159,"y":106,"style":160},[53,179],{"x":163,"y":102,"width":75,"height":76,"rx":77,"fill":164,"stroke":156},[60,181,113],{"x":167,"y":106,"style":107},[53,183],{"x":170,"y":102,"width":75,"height":76,"rx":77,"fill":164,"stroke":156},[60,185,118],{"x":173,"y":106,"style":107},[53,187],{"x":154,"y":121,"width":75,"height":76,"rx":77,"fill":155,"stroke":156,"style":80},[60,189,125],{"x":159,"y":124,"style":160},[53,191],{"x":163,"y":121,"width":75,"height":76,"rx":77,"fill":164,"stroke":156},[60,193,130],{"x":167,"y":124,"style":107},[53,195],{"x":170,"y":121,"width":75,"height":76,"rx":77,"fill":164,"stroke":156},[60,197,135],{"x":173,"y":124,"style":107},[137,199],{"d":200,"fill":140,"stroke":156,"style":141},"M532 56 V 168",[143,202],{"points":203,"fill":204},"532,176 526,164 538,164","#0f9488",[60,206,207],{"x":167,"y":149,"style":150},"yields (A2,A3,A4) then (B2,B3,B4)",[209,210,212],"h2",{"id":211},"prerequisites","Prerequisites",[214,215,220],"pre",{"className":216,"code":217,"language":218,"meta":219,"style":219},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl\n","bash","",[14,221,222],{"__ignoreMap":219},[223,224,227,231,235],"span",{"class":225,"line":226},"line",1,[223,228,230],{"class":229},"sMTad","pip",[223,232,234],{"class":233},"srMev"," install",[223,236,237],{"class":233}," openpyxl\n",[10,239,240],{},"A sheet to walk:",[214,242,246],{"className":243,"code":244,"language":245,"meta":219,"style":219},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\n\nwb = Workbook()\nws = wb.active\nws.title = \"Sales\"\nws.append([\"region\", \"branch\", \"units\", \"revenue\"])\nfor i in range(1, 26):\n    ws.append([f\"Region {i % 4}\", f\"Branch {i}\", 100 + i, 12.5 * (100 + i)])\nwb.save(\"sales.xlsx\")\n","python",[14,247,248,264,271,283,294,305,333,363,437],{"__ignoreMap":219},[223,249,250,254,258,261],{"class":225,"line":226},[223,251,253],{"class":252},"s-kum","from",[223,255,257],{"class":256},"skGVy"," openpyxl ",[223,259,260],{"class":252},"import",[223,262,263],{"class":256}," Workbook\n",[223,265,267],{"class":225,"line":266},2,[223,268,270],{"emptyLinePlaceholder":269},true,"\n",[223,272,274,277,280],{"class":225,"line":273},3,[223,275,276],{"class":256},"wb ",[223,278,279],{"class":252},"=",[223,281,282],{"class":256}," Workbook()\n",[223,284,286,289,291],{"class":225,"line":285},4,[223,287,288],{"class":256},"ws ",[223,290,279],{"class":252},[223,292,293],{"class":256}," wb.active\n",[223,295,297,300,302],{"class":225,"line":296},5,[223,298,299],{"class":256},"ws.title ",[223,301,279],{"class":252},[223,303,304],{"class":233}," \"Sales\"\n",[223,306,308,311,314,317,320,322,325,327,330],{"class":225,"line":307},6,[223,309,310],{"class":256},"ws.append([",[223,312,313],{"class":233},"\"region\"",[223,315,316],{"class":256},", ",[223,318,319],{"class":233},"\"branch\"",[223,321,316],{"class":256},[223,323,324],{"class":233},"\"units\"",[223,326,316],{"class":256},[223,328,329],{"class":233},"\"revenue\"",[223,331,332],{"class":256},"])\n",[223,334,336,339,342,345,349,352,355,357,360],{"class":225,"line":335},7,[223,337,338],{"class":252},"for",[223,340,341],{"class":256}," i ",[223,343,344],{"class":252},"in",[223,346,348],{"class":347},"sP0c6"," range",[223,350,351],{"class":256},"(",[223,353,354],{"class":347},"1",[223,356,316],{"class":256},[223,358,359],{"class":347},"26",[223,361,362],{"class":256},"):\n",[223,364,366,369,372,375,379,382,385,388,391,394,396,398,401,403,406,408,410,412,415,418,421,424,427,430,432,434],{"class":225,"line":365},8,[223,367,368],{"class":256},"    ws.append([",[223,370,371],{"class":252},"f",[223,373,374],{"class":233},"\"Region ",[223,376,378],{"class":377},"sSjpA","{",[223,380,381],{"class":256},"i ",[223,383,384],{"class":252},"%",[223,386,387],{"class":347}," 4",[223,389,390],{"class":377},"}",[223,392,393],{"class":233},"\"",[223,395,316],{"class":256},[223,397,371],{"class":252},[223,399,400],{"class":233},"\"Branch ",[223,402,378],{"class":377},[223,404,405],{"class":256},"i",[223,407,390],{"class":377},[223,409,393],{"class":233},[223,411,316],{"class":256},[223,413,414],{"class":347},"100",[223,416,417],{"class":252}," +",[223,419,420],{"class":256}," i, ",[223,422,423],{"class":347},"12.5",[223,425,426],{"class":252}," *",[223,428,429],{"class":256}," (",[223,431,414],{"class":347},[223,433,417],{"class":252},[223,435,436],{"class":256}," i)])\n",[223,438,440,443,446],{"class":225,"line":439},9,[223,441,442],{"class":256},"wb.save(",[223,444,445],{"class":233},"\"sales.xlsx\"",[223,447,448],{"class":256},")\n",[209,450,452],{"id":451},"step-1-iterate-rows","Step 1 — Iterate rows",[10,454,455,457,458,461],{},[14,456,16],{}," yields one tuple of ",[14,459,460],{},"Cell"," objects per row:",[214,463,465],{"className":243,"code":464,"language":245,"meta":219,"style":219},"from openpyxl import load_workbook\n\nwb = load_workbook(\"sales.xlsx\")\nws = wb[\"Sales\"]\n\nfor row in ws.iter_rows(min_row=2, max_row=6):\n    for cell in row:\n        print(cell.coordinate, cell.value)\n",[14,466,467,478,482,495,510,514,545,558],{"__ignoreMap":219},[223,468,469,471,473,475],{"class":225,"line":226},[223,470,253],{"class":252},[223,472,257],{"class":256},[223,474,260],{"class":252},[223,476,477],{"class":256}," load_workbook\n",[223,479,480],{"class":225,"line":266},[223,481,270],{"emptyLinePlaceholder":269},[223,483,484,486,488,491,493],{"class":225,"line":273},[223,485,276],{"class":256},[223,487,279],{"class":252},[223,489,490],{"class":256}," load_workbook(",[223,492,445],{"class":233},[223,494,448],{"class":256},[223,496,497,499,501,504,507],{"class":225,"line":285},[223,498,288],{"class":256},[223,500,279],{"class":252},[223,502,503],{"class":256}," wb[",[223,505,506],{"class":233},"\"Sales\"",[223,508,509],{"class":256},"]\n",[223,511,512],{"class":225,"line":296},[223,513,270],{"emptyLinePlaceholder":269},[223,515,516,518,521,523,526,530,532,535,537,539,541,543],{"class":225,"line":307},[223,517,338],{"class":252},[223,519,520],{"class":256}," row ",[223,522,344],{"class":252},[223,524,525],{"class":256}," ws.iter_rows(",[223,527,529],{"class":528},"sa561","min_row",[223,531,279],{"class":252},[223,533,534],{"class":347},"2",[223,536,316],{"class":256},[223,538,24],{"class":528},[223,540,279],{"class":252},[223,542,77],{"class":347},[223,544,362],{"class":256},[223,546,547,550,553,555],{"class":225,"line":335},[223,548,549],{"class":252},"    for",[223,551,552],{"class":256}," cell ",[223,554,344],{"class":252},[223,556,557],{"class":256}," row:\n",[223,559,560,563],{"class":225,"line":365},[223,561,562],{"class":347},"        print",[223,564,565],{"class":256},"(cell.coordinate, cell.value)\n",[10,567,568,569,572,573,575],{},"When you only want the data, ",[14,570,571],{},"values_only=True"," yields plain tuples instead — no ",[14,574,460],{}," objects are constructed at all:",[214,577,579],{"className":243,"code":578,"language":245,"meta":219,"style":219},"for region, branch, units, revenue in ws.iter_rows(min_row=2, values_only=True):\n    print(f\"{region:\u003C10} {branch:\u003C10} {units:>5} {revenue:>10.2f}\")\n",[14,580,581,610],{"__ignoreMap":219},[223,582,583,585,588,590,592,594,596,598,600,603,605,608],{"class":225,"line":226},[223,584,338],{"class":252},[223,586,587],{"class":256}," region, branch, units, revenue ",[223,589,344],{"class":252},[223,591,525],{"class":256},[223,593,529],{"class":528},[223,595,279],{"class":252},[223,597,534],{"class":347},[223,599,316],{"class":256},[223,601,602],{"class":528},"values_only",[223,604,279],{"class":252},[223,606,607],{"class":347},"True",[223,609,362],{"class":256},[223,611,612,615,617,619,621,623,626,629,631,634,637,639,641,643,646,649,651,653,656,659,661,663],{"class":225,"line":266},[223,613,614],{"class":347},"    print",[223,616,351],{"class":256},[223,618,371],{"class":252},[223,620,393],{"class":233},[223,622,378],{"class":377},[223,624,625],{"class":256},"region",[223,627,628],{"class":252},":\u003C10",[223,630,390],{"class":377},[223,632,633],{"class":377}," {",[223,635,636],{"class":256},"branch",[223,638,628],{"class":252},[223,640,390],{"class":377},[223,642,633],{"class":377},[223,644,645],{"class":256},"units",[223,647,648],{"class":252},":>5",[223,650,390],{"class":377},[223,652,633],{"class":377},[223,654,655],{"class":256},"revenue",[223,657,658],{"class":252},":>10.2f",[223,660,390],{"class":377},[223,662,393],{"class":233},[223,664,448],{"class":256},[10,666,667],{},"That tuple unpacking is the pattern to reach for by default. It is faster, it uses far less memory, and it reads better than indexing into a tuple of cells.",[10,669,670,671,17,674,677],{},"Bound the range when you know it. Passing ",[14,672,673],{},"min_col",[14,675,676],{},"max_col"," avoids walking columns you will ignore:",[214,679,681],{"className":243,"code":680,"language":245,"meta":219,"style":219},"# Only the units and revenue columns, rows 2 to 26.\nfor units, revenue in ws.iter_rows(min_row=2, max_row=26, min_col=3, max_col=4,\n                                   values_only=True):\n    print(units, revenue)\n",[14,682,683,689,735,746],{"__ignoreMap":219},[223,684,685],{"class":225,"line":226},[223,686,688],{"class":687},"s-wDw","# Only the units and revenue columns, rows 2 to 26.\n",[223,690,691,693,696,698,700,702,704,706,708,710,712,714,716,718,720,723,725,727,729,732],{"class":225,"line":266},[223,692,338],{"class":252},[223,694,695],{"class":256}," units, revenue ",[223,697,344],{"class":252},[223,699,525],{"class":256},[223,701,529],{"class":528},[223,703,279],{"class":252},[223,705,534],{"class":347},[223,707,316],{"class":256},[223,709,24],{"class":528},[223,711,279],{"class":252},[223,713,359],{"class":347},[223,715,316],{"class":256},[223,717,673],{"class":528},[223,719,279],{"class":252},[223,721,722],{"class":347},"3",[223,724,316],{"class":256},[223,726,676],{"class":528},[223,728,279],{"class":252},[223,730,731],{"class":347},"4",[223,733,734],{"class":256},",\n",[223,736,737,740,742,744],{"class":225,"line":273},[223,738,739],{"class":528},"                                   values_only",[223,741,279],{"class":252},[223,743,607],{"class":347},[223,745,362],{"class":256},[223,747,748,750],{"class":225,"line":285},[223,749,614],{"class":347},[223,751,752],{"class":256},"(units, revenue)\n",[10,754,755],{},"Two shorthands are worth knowing. Indexing with a range string yields the same row tuples, which reads nicely for a fixed block:",[214,757,759],{"className":243,"code":758,"language":245,"meta":219,"style":219},"for row in ws[\"A2:D6\"]:\n    print([cell.value for cell in row])\n",[14,760,761,778],{"__ignoreMap":219},[223,762,763,765,767,769,772,775],{"class":225,"line":226},[223,764,338],{"class":252},[223,766,520],{"class":256},[223,768,344],{"class":252},[223,770,771],{"class":256}," ws[",[223,773,774],{"class":233},"\"A2:D6\"",[223,776,777],{"class":256},"]:\n",[223,779,780,782,785,787,789,791],{"class":225,"line":266},[223,781,614],{"class":347},[223,783,784],{"class":256},"([cell.value ",[223,786,338],{"class":252},[223,788,552],{"class":256},[223,790,344],{"class":252},[223,792,793],{"class":256}," row])\n",[10,795,796,797,800,801,803,804,806],{},"And ",[14,798,799],{},"ws.rows"," is an alias for the whole sheet with no options — convenient interactively, but it offers neither bounds nor ",[14,802,602],{},", so prefer ",[14,805,16],{}," in real code.",[209,808,810],{"id":809},"step-2-iterate-columns","Step 2 — Iterate columns",[10,812,813,815],{},[14,814,20],{}," is the transpose, yielding one tuple per column. It is the natural shape when you want to summarise a column or check its type:",[214,817,819],{"className":243,"code":818,"language":245,"meta":219,"style":219},"for column in ws.iter_cols(min_row=2, min_col=3, max_col=4, values_only=True):\n    numbers = [v for v in column if isinstance(v, (int, float))]\n    print(f\"n={len(numbers)}  total={sum(numbers):,.2f}  max={max(numbers):,.2f}\")\n",[14,820,821,865,904],{"__ignoreMap":219},[223,822,823,825,828,830,833,835,837,839,841,843,845,847,849,851,853,855,857,859,861,863],{"class":225,"line":226},[223,824,338],{"class":252},[223,826,827],{"class":256}," column ",[223,829,344],{"class":252},[223,831,832],{"class":256}," ws.iter_cols(",[223,834,529],{"class":528},[223,836,279],{"class":252},[223,838,534],{"class":347},[223,840,316],{"class":256},[223,842,673],{"class":528},[223,844,279],{"class":252},[223,846,722],{"class":347},[223,848,316],{"class":256},[223,850,676],{"class":528},[223,852,279],{"class":252},[223,854,731],{"class":347},[223,856,316],{"class":256},[223,858,602],{"class":528},[223,860,279],{"class":252},[223,862,607],{"class":347},[223,864,362],{"class":256},[223,866,867,870,872,875,877,880,882,884,887,890,893,896,898,901],{"class":225,"line":266},[223,868,869],{"class":256},"    numbers ",[223,871,279],{"class":252},[223,873,874],{"class":256}," [v ",[223,876,338],{"class":252},[223,878,879],{"class":256}," v ",[223,881,344],{"class":252},[223,883,827],{"class":256},[223,885,886],{"class":252},"if",[223,888,889],{"class":347}," isinstance",[223,891,892],{"class":256},"(v, (",[223,894,895],{"class":347},"int",[223,897,316],{"class":256},[223,899,900],{"class":347},"float",[223,902,903],{"class":256},"))]\n",[223,905,906,908,910,912,915,917,920,923,925,928,930,933,935,938,940,943,945,948,950,952,954,956],{"class":225,"line":273},[223,907,614],{"class":347},[223,909,351],{"class":256},[223,911,371],{"class":252},[223,913,914],{"class":233},"\"n=",[223,916,378],{"class":377},[223,918,919],{"class":347},"len",[223,921,922],{"class":256},"(numbers)",[223,924,390],{"class":377},[223,926,927],{"class":233},"  total=",[223,929,378],{"class":377},[223,931,932],{"class":347},"sum",[223,934,922],{"class":256},[223,936,937],{"class":252},":,.2f",[223,939,390],{"class":377},[223,941,942],{"class":233},"  max=",[223,944,378],{"class":377},[223,946,947],{"class":347},"max",[223,949,922],{"class":256},[223,951,937],{"class":252},[223,953,390],{"class":377},[223,955,393],{"class":233},[223,957,448],{"class":256},[10,959,960,961,967],{},"One important limitation: ",[962,963,964,966],"strong",{},[14,965,20],{}," does not work in read-only mode",". Read-only mode streams the file row by row, and producing a column would require holding the whole sheet. If you need column-wise access on a large file, iterate rows and transpose in memory, or read with pandas instead.",[209,969,971],{"id":970},"step-3-the-max_row-trap","Step 3 — The max_row trap",[10,973,974,977,978,982,983,985],{},[14,975,976],{},"ws.max_row"," reports the extent of the ",[979,980,981],"em",{},"used range",", not the number of populated rows. A cell that was formatted and then cleared, a stray space in row 40,000, or rows that were deleted without clearing their formatting all extend it — so a sheet with 25 data rows can report ",[14,984,24],{}," of 1,048,576 and your loop runs for a very long time over nothing.",[33,987,43,993,43,996,43,999,43,1002,43,1007,43,1013,43,1018,43,1023,43,1028,43,1032,43,1037,43,1041,43,1045,43,1048,43,1051,43,1054,43,1058,43,1065,43,1070,43,1073,43,1075,43,1079,43,1082],{"viewBox":988,"role":36,"ariaLabel":989,"ariaLabelledBy":990,"xmlns":41,"style":42},"0 0 800 232","Used range versus populated rows: data occupies rows two to twenty-six, but a formatted empty cell far below extends max_row, so a loop bounded by max_row iterates thousands of empty rows.",[991,992],"maxrow-t","maxrow-d",[45,994,995],{"id":991},"Why max_row is not the number of rows with data",[49,997,998],{"id":992},"A sheet where rows two to twenty-six hold data, followed by a large expanse of empty rows, and then a single formatted but empty cell at row forty thousand. Excel's used range therefore extends to row forty thousand, and max_row reports that. A loop bounded by max_row walks nearly forty thousand empty rows. Breaking when the key column is blank stops at row twenty-seven instead.",[53,1000],{"x":55,"y":55,"width":56,"height":1001,"fill":58},"232",[53,1003],{"x":76,"y":76,"width":1004,"height":1005,"rx":1006,"fill":155,"stroke":156,"style":80},"300","44","8",[60,1008,1012],{"x":1009,"y":1010,"style":1011},"190","60","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","rows 2–26",[60,1014,1017],{"x":1009,"y":1015,"style":1016},"77","font-size:10.5px;fill:var(--muted,#5b6780);text-anchor:middle","actual data",[53,1019],{"x":76,"y":102,"width":1004,"height":1020,"rx":1006,"fill":1021,"stroke":1022,"style":80},"70","#f0f2f5","var(--line,#cdd5e6)",[60,1024,1027],{"x":1009,"y":1025,"style":1026},"122","font-size:11.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","rows 27–39,999",[60,1029,1031],{"x":1009,"y":1030,"style":1016},"142","entirely empty",[53,1033],{"x":76,"y":1034,"width":1004,"height":1005,"rx":1006,"fill":1035,"stroke":1036,"style":80},"170","#fee8f2","var(--accent,#f43f8f)",[60,1038,1040],{"x":1009,"y":1009,"style":1039},"font-size:11.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","row 40,000",[60,1042,1044],{"x":1009,"y":1043,"style":1016},"207","formatted once, now blank",[137,1046],{"d":1047,"fill":140,"stroke":156,"style":80},"M348 62 H 386",[143,1049],{"points":1050,"fill":204},"394,62 382,56 382,68",[137,1052],{"d":1053,"fill":140,"stroke":1036,"style":80},"M348 192 H 386",[143,1055],{"points":1056,"fill":1057},"394,192 382,186 382,198","#f43f8f",[53,1059],{"x":1060,"y":1061,"width":1062,"height":1063,"rx":1064,"fill":155,"stroke":156,"style":80},"402","36","382","52","11",[60,1066,1069],{"x":1067,"y":1068,"style":1011},"593","58","break on a blank key column",[60,1071,1072],{"x":1067,"y":1015,"style":1016},"stops at row 27 · 25 iterations",[53,1074],{"x":1060,"y":89,"width":1062,"height":1063,"rx":1064,"fill":1035,"stroke":1036,"style":80},[60,1076,1078],{"x":1067,"y":1077,"style":1039},"188","max_row says 40,000",[60,1080,1081],{"x":1067,"y":1043,"style":1016},"39,974 wasted iterations",[60,1083,1085],{"x":1067,"y":1084,"style":150},"128","the used range is a formatting fact, not a data fact",[10,1087,1088],{},"Stop on the data instead:",[214,1090,1092],{"className":243,"code":1091,"language":245,"meta":219,"style":219},"from openpyxl import load_workbook\n\ndef iter_data_rows(ws, key_col=1, min_row=2, stop_after_blanks=1):\n    \"\"\"Yield row tuples until the key column has been blank N times running.\"\"\"\n    blanks = 0\n    for row in ws.iter_rows(min_row=min_row, values_only=True):\n        if row[key_col - 1] in (None, \"\"):\n            blanks += 1\n            if blanks >= stop_after_blanks:\n                return\n            continue\n        blanks = 0\n        yield row\n\nwb = load_workbook(\"sales.xlsx\", read_only=True)\nws = wb[\"Sales\"]\nrows = list(iter_data_rows(ws))\nprint(len(rows))       # 25, not 40,000\nwb.close()\n",[14,1093,1094,1104,1108,1140,1145,1155,1180,1211,1222,1236,1242,1248,1258,1267,1272,1294,1307,1321,1337],{"__ignoreMap":219},[223,1095,1096,1098,1100,1102],{"class":225,"line":226},[223,1097,253],{"class":252},[223,1099,257],{"class":256},[223,1101,260],{"class":252},[223,1103,477],{"class":256},[223,1105,1106],{"class":225,"line":266},[223,1107,270],{"emptyLinePlaceholder":269},[223,1109,1110,1113,1117,1120,1122,1124,1127,1129,1131,1134,1136,1138],{"class":225,"line":273},[223,1111,1112],{"class":252},"def",[223,1114,1116],{"class":1115},"s_Opv"," iter_data_rows",[223,1118,1119],{"class":256},"(ws, key_col",[223,1121,279],{"class":252},[223,1123,354],{"class":347},[223,1125,1126],{"class":256},", min_row",[223,1128,279],{"class":252},[223,1130,534],{"class":347},[223,1132,1133],{"class":256},", stop_after_blanks",[223,1135,279],{"class":252},[223,1137,354],{"class":347},[223,1139,362],{"class":256},[223,1141,1142],{"class":225,"line":285},[223,1143,1144],{"class":233},"    \"\"\"Yield row tuples until the key column has been blank N times running.\"\"\"\n",[223,1146,1147,1150,1152],{"class":225,"line":296},[223,1148,1149],{"class":256},"    blanks ",[223,1151,279],{"class":252},[223,1153,1154],{"class":347}," 0\n",[223,1156,1157,1159,1161,1163,1165,1167,1169,1172,1174,1176,1178],{"class":225,"line":307},[223,1158,549],{"class":252},[223,1160,520],{"class":256},[223,1162,344],{"class":252},[223,1164,525],{"class":256},[223,1166,529],{"class":528},[223,1168,279],{"class":252},[223,1170,1171],{"class":256},"min_row, ",[223,1173,602],{"class":528},[223,1175,279],{"class":252},[223,1177,607],{"class":347},[223,1179,362],{"class":256},[223,1181,1182,1185,1188,1191,1194,1197,1199,1201,1204,1206,1209],{"class":225,"line":335},[223,1183,1184],{"class":252},"        if",[223,1186,1187],{"class":256}," row[key_col ",[223,1189,1190],{"class":252},"-",[223,1192,1193],{"class":347}," 1",[223,1195,1196],{"class":256},"] ",[223,1198,344],{"class":252},[223,1200,429],{"class":256},[223,1202,1203],{"class":347},"None",[223,1205,316],{"class":256},[223,1207,1208],{"class":233},"\"\"",[223,1210,362],{"class":256},[223,1212,1213,1216,1219],{"class":225,"line":365},[223,1214,1215],{"class":256},"            blanks ",[223,1217,1218],{"class":252},"+=",[223,1220,1221],{"class":347}," 1\n",[223,1223,1224,1227,1230,1233],{"class":225,"line":439},[223,1225,1226],{"class":252},"            if",[223,1228,1229],{"class":256}," blanks ",[223,1231,1232],{"class":252},">=",[223,1234,1235],{"class":256}," stop_after_blanks:\n",[223,1237,1239],{"class":225,"line":1238},10,[223,1240,1241],{"class":252},"                return\n",[223,1243,1245],{"class":225,"line":1244},11,[223,1246,1247],{"class":252},"            continue\n",[223,1249,1251,1254,1256],{"class":225,"line":1250},12,[223,1252,1253],{"class":256},"        blanks ",[223,1255,279],{"class":252},[223,1257,1154],{"class":347},[223,1259,1261,1264],{"class":225,"line":1260},13,[223,1262,1263],{"class":252},"        yield",[223,1265,1266],{"class":256}," row\n",[223,1268,1270],{"class":225,"line":1269},14,[223,1271,270],{"emptyLinePlaceholder":269},[223,1273,1275,1277,1279,1281,1283,1285,1288,1290,1292],{"class":225,"line":1274},15,[223,1276,276],{"class":256},[223,1278,279],{"class":252},[223,1280,490],{"class":256},[223,1282,445],{"class":233},[223,1284,316],{"class":256},[223,1286,1287],{"class":528},"read_only",[223,1289,279],{"class":252},[223,1291,607],{"class":347},[223,1293,448],{"class":256},[223,1295,1297,1299,1301,1303,1305],{"class":225,"line":1296},16,[223,1298,288],{"class":256},[223,1300,279],{"class":252},[223,1302,503],{"class":256},[223,1304,506],{"class":233},[223,1306,509],{"class":256},[223,1308,1310,1313,1315,1318],{"class":225,"line":1309},17,[223,1311,1312],{"class":256},"rows ",[223,1314,279],{"class":252},[223,1316,1317],{"class":347}," list",[223,1319,1320],{"class":256},"(iter_data_rows(ws))\n",[223,1322,1324,1327,1329,1331,1334],{"class":225,"line":1323},18,[223,1325,1326],{"class":347},"print",[223,1328,351],{"class":256},[223,1330,919],{"class":347},[223,1332,1333],{"class":256},"(rows))       ",[223,1335,1336],{"class":687},"# 25, not 40,000\n",[223,1338,1340],{"class":225,"line":1339},19,[223,1341,1342],{"class":256},"wb.close()\n",[10,1344,1345,1346,1349],{},"The ",[14,1347,1348],{},"stop_after_blanks"," parameter matters for sheets with a deliberate blank separator row between blocks — set it to 2 or 3 and a single gap does not end the read early.",[209,1351,1353],{"id":1352},"step-4-key-rows-by-their-header","Step 4 — Key rows by their header",[33,1355,43,1360,43,1363,43,1366,43,1368,43,1373,43,1377,43,1381,43,1386,43,1391,43,1394,43,1397,43,1400,43,1403,43,1409,43,1414,43,1418,43,1423,43,1427,43,1431,43,1434,43,1438,43,1441,43,1445,43,1448],{"viewBox":988,"role":36,"ariaLabel":1356,"ariaLabelledBy":1357,"xmlns":41,"style":42},"Positional unpacking breaks when a column is inserted, while a dict keyed by header name keeps working because it looks columns up by name.",[1358,1359],"keyed-t","keyed-d",[45,1361,1362],{"id":1358},"Why positional row unpacking breaks and header keys do not",[49,1364,1365],{"id":1359},"Two readers against a sheet that gains an owner column in position two. Positional unpacking assigns branch to what is now the owner value and units to the branch name, so every downstream calculation silently uses the wrong field. A dict keyed by header name looks each field up by its column heading, so inserting a column changes nothing for the reader.",[53,1367],{"x":55,"y":55,"width":56,"height":1001,"fill":58},[60,1369,1372],{"x":1370,"y":359,"style":1371},"400","font-size:12px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","an \"owner\" column is inserted in position 2",[53,1374],{"x":76,"y":76,"width":1375,"height":63,"rx":1376,"fill":78,"stroke":79,"style":80},"152","5",[60,1378,625],{"x":1379,"y":1010,"style":1380},"116","font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[53,1382],{"x":1383,"y":76,"width":1375,"height":63,"rx":1376,"fill":1384,"stroke":1385,"style":80},"198","#fdefd8","var(--gold,#b4740a)",[60,1387,1390],{"x":1388,"y":1010,"style":1389},"274","font-size:10.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","owner (new)",[53,1392],{"x":1393,"y":76,"width":1375,"height":63,"rx":1376,"fill":78,"stroke":79,"style":80},"356",[60,1395,636],{"x":1396,"y":1010,"style":1380},"432",[53,1398],{"x":1399,"y":76,"width":1375,"height":63,"rx":1376,"fill":78,"stroke":79,"style":80},"514",[60,1401,645],{"x":1402,"y":1010,"style":1380},"590",[53,1404],{"x":1405,"y":1406,"width":1407,"height":1084,"rx":1408,"fill":1035,"stroke":1036,"style":80},"14","86","368","13",[60,1410,1413],{"x":1383,"y":1411,"style":1412},"112","font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","positional unpacking",[60,1415,1417],{"x":1383,"y":121,"style":1416},"font-size:10.5px;fill:var(--text,#172033);text-anchor:middle","region, branch, units = row",[60,1419,1422],{"x":1383,"y":1420,"style":1421},"164","font-size:10.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","branch now holds the owner",[60,1424,1426],{"x":1383,"y":1425,"style":1016},"186","units now holds the branch name",[60,1428,1430],{"x":1383,"y":1429,"style":1016},"206","no error — just wrong numbers",[53,1432],{"x":1433,"y":1406,"width":1407,"height":1084,"rx":1408,"fill":155,"stroke":156,"style":80},"418",[60,1435,1437],{"x":1436,"y":1411,"style":69},"602","dict keyed by header",[60,1439,1440],{"x":1436,"y":121,"style":1416},"record[\"branch\"], record[\"units\"]",[60,1442,1444],{"x":1436,"y":1420,"style":1443},"font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","still resolve correctly",[60,1446,1447],{"x":1436,"y":1425,"style":1016},"the new column is simply ignored",[60,1449,1450],{"x":1436,"y":1429,"style":1016},"and a renamed one raises KeyError",[10,1452,1453],{},"Positional unpacking breaks the day somebody inserts a column. Build a dict per row instead, keyed by the header text:",[214,1455,1457],{"className":243,"code":1456,"language":245,"meta":219,"style":219},"from openpyxl import load_workbook\n\ndef read_records(path, sheet_name=None, header_row=1):\n    \"\"\"Yield each data row as a dict keyed by its column header.\"\"\"\n    wb = load_workbook(path, read_only=True, data_only=True)\n    ws = wb[sheet_name] if sheet_name else wb.active\n    try:\n        rows = ws.iter_rows(min_row=header_row, values_only=True)\n        headers = [\n            str(h).strip() if h is not None else f\"column_{i}\"\n            for i, h in enumerate(next(rows))\n        ]\n        for values in rows:\n            if all(v is None for v in values):\n                continue\n            yield dict(zip(headers, values))\n    finally:\n        wb.close()\n\nfor record in read_records(\"sales.xlsx\"):\n    if record[\"units\"] > 120:\n        print(record[\"branch\"], record[\"revenue\"])\n",[14,1458,1459,1469,1473,1496,1501,1528,1548,1556,1580,1590,1630,1651,1656,1669,1693,1698,1714,1721,1726,1730,1747,1768],{"__ignoreMap":219},[223,1460,1461,1463,1465,1467],{"class":225,"line":226},[223,1462,253],{"class":252},[223,1464,257],{"class":256},[223,1466,260],{"class":252},[223,1468,477],{"class":256},[223,1470,1471],{"class":225,"line":266},[223,1472,270],{"emptyLinePlaceholder":269},[223,1474,1475,1477,1480,1483,1485,1487,1490,1492,1494],{"class":225,"line":273},[223,1476,1112],{"class":252},[223,1478,1479],{"class":1115}," read_records",[223,1481,1482],{"class":256},"(path, sheet_name",[223,1484,279],{"class":252},[223,1486,1203],{"class":347},[223,1488,1489],{"class":256},", header_row",[223,1491,279],{"class":252},[223,1493,354],{"class":347},[223,1495,362],{"class":256},[223,1497,1498],{"class":225,"line":285},[223,1499,1500],{"class":233},"    \"\"\"Yield each data row as a dict keyed by its column header.\"\"\"\n",[223,1502,1503,1506,1508,1511,1513,1515,1517,1519,1522,1524,1526],{"class":225,"line":296},[223,1504,1505],{"class":256},"    wb ",[223,1507,279],{"class":252},[223,1509,1510],{"class":256}," load_workbook(path, ",[223,1512,1287],{"class":528},[223,1514,279],{"class":252},[223,1516,607],{"class":347},[223,1518,316],{"class":256},[223,1520,1521],{"class":528},"data_only",[223,1523,279],{"class":252},[223,1525,607],{"class":347},[223,1527,448],{"class":256},[223,1529,1530,1533,1535,1538,1540,1543,1546],{"class":225,"line":307},[223,1531,1532],{"class":256},"    ws ",[223,1534,279],{"class":252},[223,1536,1537],{"class":256}," wb[sheet_name] ",[223,1539,886],{"class":252},[223,1541,1542],{"class":256}," sheet_name ",[223,1544,1545],{"class":252},"else",[223,1547,293],{"class":256},[223,1549,1550,1553],{"class":225,"line":335},[223,1551,1552],{"class":252},"    try",[223,1554,1555],{"class":256},":\n",[223,1557,1558,1561,1563,1565,1567,1569,1572,1574,1576,1578],{"class":225,"line":365},[223,1559,1560],{"class":256},"        rows ",[223,1562,279],{"class":252},[223,1564,525],{"class":256},[223,1566,529],{"class":528},[223,1568,279],{"class":252},[223,1570,1571],{"class":256},"header_row, ",[223,1573,602],{"class":528},[223,1575,279],{"class":252},[223,1577,607],{"class":347},[223,1579,448],{"class":256},[223,1581,1582,1585,1587],{"class":225,"line":439},[223,1583,1584],{"class":256},"        headers ",[223,1586,279],{"class":252},[223,1588,1589],{"class":256}," [\n",[223,1591,1592,1595,1598,1600,1603,1606,1609,1612,1615,1618,1621,1623,1625,1627],{"class":225,"line":1238},[223,1593,1594],{"class":347},"            str",[223,1596,1597],{"class":256},"(h).strip() ",[223,1599,886],{"class":252},[223,1601,1602],{"class":256}," h ",[223,1604,1605],{"class":252},"is",[223,1607,1608],{"class":252}," not",[223,1610,1611],{"class":347}," None",[223,1613,1614],{"class":252}," else",[223,1616,1617],{"class":252}," f",[223,1619,1620],{"class":233},"\"column_",[223,1622,378],{"class":377},[223,1624,405],{"class":256},[223,1626,390],{"class":377},[223,1628,1629],{"class":233},"\"\n",[223,1631,1632,1635,1638,1640,1643,1645,1648],{"class":225,"line":1244},[223,1633,1634],{"class":252},"            for",[223,1636,1637],{"class":256}," i, h ",[223,1639,344],{"class":252},[223,1641,1642],{"class":347}," enumerate",[223,1644,351],{"class":256},[223,1646,1647],{"class":347},"next",[223,1649,1650],{"class":256},"(rows))\n",[223,1652,1653],{"class":225,"line":1250},[223,1654,1655],{"class":256},"        ]\n",[223,1657,1658,1661,1664,1666],{"class":225,"line":1260},[223,1659,1660],{"class":252},"        for",[223,1662,1663],{"class":256}," values ",[223,1665,344],{"class":252},[223,1667,1668],{"class":256}," rows:\n",[223,1670,1671,1673,1676,1679,1681,1683,1686,1688,1690],{"class":225,"line":1269},[223,1672,1226],{"class":252},[223,1674,1675],{"class":347}," all",[223,1677,1678],{"class":256},"(v ",[223,1680,1605],{"class":252},[223,1682,1611],{"class":347},[223,1684,1685],{"class":252}," for",[223,1687,879],{"class":256},[223,1689,344],{"class":252},[223,1691,1692],{"class":256}," values):\n",[223,1694,1695],{"class":225,"line":1274},[223,1696,1697],{"class":252},"                continue\n",[223,1699,1700,1703,1706,1708,1711],{"class":225,"line":1296},[223,1701,1702],{"class":252},"            yield",[223,1704,1705],{"class":347}," dict",[223,1707,351],{"class":256},[223,1709,1710],{"class":347},"zip",[223,1712,1713],{"class":256},"(headers, values))\n",[223,1715,1716,1719],{"class":225,"line":1309},[223,1717,1718],{"class":252},"    finally",[223,1720,1555],{"class":256},[223,1722,1723],{"class":225,"line":1323},[223,1724,1725],{"class":256},"        wb.close()\n",[223,1727,1728],{"class":225,"line":1339},[223,1729,270],{"emptyLinePlaceholder":269},[223,1731,1733,1735,1738,1740,1743,1745],{"class":225,"line":1732},20,[223,1734,338],{"class":252},[223,1736,1737],{"class":256}," record ",[223,1739,344],{"class":252},[223,1741,1742],{"class":256}," read_records(",[223,1744,445],{"class":233},[223,1746,362],{"class":256},[223,1748,1750,1753,1756,1758,1760,1763,1766],{"class":225,"line":1749},21,[223,1751,1752],{"class":252},"    if",[223,1754,1755],{"class":256}," record[",[223,1757,324],{"class":233},[223,1759,1196],{"class":256},[223,1761,1762],{"class":252},">",[223,1764,1765],{"class":347}," 120",[223,1767,1555],{"class":256},[223,1769,1771,1773,1776,1778,1781,1783],{"class":225,"line":1770},22,[223,1772,562],{"class":347},[223,1774,1775],{"class":256},"(record[",[223,1777,319],{"class":233},[223,1779,1780],{"class":256},"], record[",[223,1782,329],{"class":233},[223,1784,332],{"class":256},[10,1786,1787,1790,1791,1794,1795,31],{},[14,1788,1789],{},"data_only=True"," returns the cached ",[979,1792,1793],{},"result"," of a formula rather than the formula text, which is what a reading pass almost always wants — the distinction is covered in ",[27,1796,1798],{"href":1797},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fread-formula-results-with-openpyxl-data-only\u002F","reading formula results with openpyxl data_only",[209,1800,1802],{"id":1801},"step-5-write-while-you-iterate","Step 5 — Write while you iterate",[10,1804,1805,1806,1808],{},"Iterating over ",[14,1807,460],{}," objects lets you write back in the same pass, which is the core of any template-filling or formatting job:",[214,1810,1812],{"className":243,"code":1811,"language":245,"meta":219,"style":219},"from openpyxl import load_workbook\nfrom openpyxl.styles import Font, PatternFill\n\nwb = load_workbook(\"sales.xlsx\")          # NOT read_only — we are writing\nws = wb[\"Sales\"]\n\nflag = PatternFill(\"solid\", fgColor=\"FDEFD8\")\nbold = Font(bold=True)\n\nfor row in ws.iter_rows(min_row=2, max_row=ws.max_row, min_col=3, max_col=4):\n    units, revenue = row\n    if units.value and units.value > 120:\n        units.fill = flag\n        revenue.fill = flag\n        revenue.font = bold\n\nwb.save(\"sales_flagged.xlsx\")\n",[14,1813,1814,1824,1836,1840,1856,1868,1872,1897,1916,1920,1961,1970,1988,1998,2007,2017,2021],{"__ignoreMap":219},[223,1815,1816,1818,1820,1822],{"class":225,"line":226},[223,1817,253],{"class":252},[223,1819,257],{"class":256},[223,1821,260],{"class":252},[223,1823,477],{"class":256},[223,1825,1826,1828,1831,1833],{"class":225,"line":266},[223,1827,253],{"class":252},[223,1829,1830],{"class":256}," openpyxl.styles ",[223,1832,260],{"class":252},[223,1834,1835],{"class":256}," Font, PatternFill\n",[223,1837,1838],{"class":225,"line":273},[223,1839,270],{"emptyLinePlaceholder":269},[223,1841,1842,1844,1846,1848,1850,1853],{"class":225,"line":285},[223,1843,276],{"class":256},[223,1845,279],{"class":252},[223,1847,490],{"class":256},[223,1849,445],{"class":233},[223,1851,1852],{"class":256},")          ",[223,1854,1855],{"class":687},"# NOT read_only — we are writing\n",[223,1857,1858,1860,1862,1864,1866],{"class":225,"line":296},[223,1859,288],{"class":256},[223,1861,279],{"class":252},[223,1863,503],{"class":256},[223,1865,506],{"class":233},[223,1867,509],{"class":256},[223,1869,1870],{"class":225,"line":307},[223,1871,270],{"emptyLinePlaceholder":269},[223,1873,1874,1877,1879,1882,1885,1887,1890,1892,1895],{"class":225,"line":335},[223,1875,1876],{"class":256},"flag ",[223,1878,279],{"class":252},[223,1880,1881],{"class":256}," PatternFill(",[223,1883,1884],{"class":233},"\"solid\"",[223,1886,316],{"class":256},[223,1888,1889],{"class":528},"fgColor",[223,1891,279],{"class":252},[223,1893,1894],{"class":233},"\"FDEFD8\"",[223,1896,448],{"class":256},[223,1898,1899,1902,1904,1907,1910,1912,1914],{"class":225,"line":365},[223,1900,1901],{"class":256},"bold ",[223,1903,279],{"class":252},[223,1905,1906],{"class":256}," Font(",[223,1908,1909],{"class":528},"bold",[223,1911,279],{"class":252},[223,1913,607],{"class":347},[223,1915,448],{"class":256},[223,1917,1918],{"class":225,"line":439},[223,1919,270],{"emptyLinePlaceholder":269},[223,1921,1922,1924,1926,1928,1930,1932,1934,1936,1938,1940,1942,1945,1947,1949,1951,1953,1955,1957,1959],{"class":225,"line":1238},[223,1923,338],{"class":252},[223,1925,520],{"class":256},[223,1927,344],{"class":252},[223,1929,525],{"class":256},[223,1931,529],{"class":528},[223,1933,279],{"class":252},[223,1935,534],{"class":347},[223,1937,316],{"class":256},[223,1939,24],{"class":528},[223,1941,279],{"class":252},[223,1943,1944],{"class":256},"ws.max_row, ",[223,1946,673],{"class":528},[223,1948,279],{"class":252},[223,1950,722],{"class":347},[223,1952,316],{"class":256},[223,1954,676],{"class":528},[223,1956,279],{"class":252},[223,1958,731],{"class":347},[223,1960,362],{"class":256},[223,1962,1963,1966,1968],{"class":225,"line":1244},[223,1964,1965],{"class":256},"    units, revenue ",[223,1967,279],{"class":252},[223,1969,1266],{"class":256},[223,1971,1972,1974,1977,1980,1982,1984,1986],{"class":225,"line":1250},[223,1973,1752],{"class":252},[223,1975,1976],{"class":256}," units.value ",[223,1978,1979],{"class":252},"and",[223,1981,1976],{"class":256},[223,1983,1762],{"class":252},[223,1985,1765],{"class":347},[223,1987,1555],{"class":256},[223,1989,1990,1993,1995],{"class":225,"line":1260},[223,1991,1992],{"class":256},"        units.fill ",[223,1994,279],{"class":252},[223,1996,1997],{"class":256}," flag\n",[223,1999,2000,2003,2005],{"class":225,"line":1269},[223,2001,2002],{"class":256},"        revenue.fill ",[223,2004,279],{"class":252},[223,2006,1997],{"class":256},[223,2008,2009,2012,2014],{"class":225,"line":1274},[223,2010,2011],{"class":256},"        revenue.font ",[223,2013,279],{"class":252},[223,2015,2016],{"class":256}," bold\n",[223,2018,2019],{"class":225,"line":1296},[223,2020,270],{"emptyLinePlaceholder":269},[223,2022,2023,2025,2028],{"class":225,"line":1309},[223,2024,442],{"class":256},[223,2026,2027],{"class":233},"\"sales_flagged.xlsx\"",[223,2029,448],{"class":256},[10,2031,2032,2033,2036,2037,31],{},"Note the constraint: writing needs a normal (not read-only) workbook, and modifying cells while iterating the ",[979,2034,2035],{},"same"," range is safe only because you are changing values and styles, not the sheet's shape. Inserting or deleting rows mid-iteration invalidates the iterator — do that in a separate pass, as described in ",[27,2038,2040],{"href":2039},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Finsert-and-delete-rows-and-columns-with-openpyxl\u002F","inserting and deleting rows and columns with openpyxl",[209,2042,2044],{"id":2043},"common-pitfalls-and-fixes","Common pitfalls and fixes",[2046,2047,2048,2064],"table",{},[2049,2050,2051],"thead",{},[2052,2053,2054,2058,2061],"tr",{},[2055,2056,2057],"th",{},"Symptom",[2055,2059,2060],{},"Cause",[2055,2062,2063],{},"Fix",[2065,2066,2067,2081,2102,2115,2134,2150,2168,2179],"tbody",{},[2052,2068,2069,2073,2078],{},[2070,2071,2072],"td",{},"Loop runs for minutes over an empty sheet",[2070,2074,2075,2077],{},[14,2076,24],{}," reflects the used range",[2070,2079,2080],{},"Break on a blank key column.",[2052,2082,2083,2088,2093],{},[2070,2084,2085],{},[14,2086,2087],{},"AttributeError: 'tuple' object has no attribute 'value'",[2070,2089,2090,2092],{},[14,2091,571],{}," yields values, not cells",[2070,2094,2095,2096,2099,2100,31],{},"Drop ",[14,2097,2098],{},".value",", or drop ",[14,2101,602],{},[2052,2103,2104,2109,2112],{},[2070,2105,2106,2108],{},[14,2107,20],{}," raises in read-only mode",[2070,2110,2111],{},"Column access needs the whole sheet",[2070,2113,2114],{},"Iterate rows and transpose.",[2052,2116,2117,2124,2129],{},[2070,2118,2119,2120,2123],{},"Formulas come back as ",[14,2121,2122],{},"=SUM(...)"," strings",[2070,2125,2126,2127],{},"Workbook opened without ",[14,2128,1521],{},[2070,2130,2131,2132,31],{},"Load with ",[14,2133,1789],{},[2052,2135,2136,2144,2147],{},[2070,2137,2138,2139,2141,2142],{},"Values are ",[14,2140,1203],{}," with ",[14,2143,1789],{},[2070,2145,2146],{},"No cached result — never opened in Excel",[2070,2148,2149],{},"Compute in Python, or open and save once.",[2052,2151,2152,2155,2160],{},[2070,2153,2154],{},"Loop is very slow on a big file",[2070,2156,2157,2159],{},[14,2158,460],{}," objects built for every cell",[2070,2161,2162,2165,2166,31],{},[14,2163,2164],{},"read_only=True"," plus ",[14,2167,571],{},[2052,2169,2170,2173,2176],{},[2070,2171,2172],{},"Row unpacking breaks after a column is added",[2070,2174,2175],{},"Positional access",[2070,2177,2178],{},"Key rows by header name.",[2052,2180,2181,2184,2187],{},[2070,2182,2183],{},"Blank rows appear in the output",[2070,2185,2186],{},"Sheet has interior gaps",[2070,2188,2189,2190,31],{},"Skip rows where every value is ",[14,2191,1203],{},[209,2193,2195],{"id":2194},"performance-and-scale-notes","Performance and scale notes",[10,2197,2198],{},"Two flags dominate iteration cost, and they compose:",[214,2200,2202],{"className":243,"code":2201,"language":245,"meta":219,"style":219},"import time\nfrom openpyxl import load_workbook\n\nfor label, kwargs, values_only in [\n    (\"normal, cells\",      {},                    False),\n    (\"normal, values\",     {},                    True),\n    (\"read-only, values\",  {\"read_only\": True},   True),\n]:\n    start = time.perf_counter()\n    wb = load_workbook(\"sales.xlsx\", **kwargs)\n    total = sum(\n        r[2] for r in wb[\"Sales\"].iter_rows(min_row=2, values_only=values_only)\n        if values_only and isinstance(r[2], (int, float))\n    ) if values_only else 0\n    wb.close()\n    print(f\"{label:\u003C20} {time.perf_counter() - start:6.3f}s\")\n",[14,2203,2204,2211,2221,2225,2236,2253,2267,2292,2296,2306,2324,2337,2375,2403,2416,2421],{"__ignoreMap":219},[223,2205,2206,2208],{"class":225,"line":226},[223,2207,260],{"class":252},[223,2209,2210],{"class":256}," time\n",[223,2212,2213,2215,2217,2219],{"class":225,"line":266},[223,2214,253],{"class":252},[223,2216,257],{"class":256},[223,2218,260],{"class":252},[223,2220,477],{"class":256},[223,2222,2223],{"class":225,"line":273},[223,2224,270],{"emptyLinePlaceholder":269},[223,2226,2227,2229,2232,2234],{"class":225,"line":285},[223,2228,338],{"class":252},[223,2230,2231],{"class":256}," label, kwargs, values_only ",[223,2233,344],{"class":252},[223,2235,1589],{"class":256},[223,2237,2238,2241,2244,2247,2250],{"class":225,"line":296},[223,2239,2240],{"class":256},"    (",[223,2242,2243],{"class":233},"\"normal, cells\"",[223,2245,2246],{"class":256},",      {},                    ",[223,2248,2249],{"class":347},"False",[223,2251,2252],{"class":256},"),\n",[223,2254,2255,2257,2260,2263,2265],{"class":225,"line":307},[223,2256,2240],{"class":256},[223,2258,2259],{"class":233},"\"normal, values\"",[223,2261,2262],{"class":256},",     {},                    ",[223,2264,607],{"class":347},[223,2266,2252],{"class":256},[223,2268,2269,2271,2274,2277,2280,2283,2285,2288,2290],{"class":225,"line":335},[223,2270,2240],{"class":256},[223,2272,2273],{"class":233},"\"read-only, values\"",[223,2275,2276],{"class":256},",  {",[223,2278,2279],{"class":233},"\"read_only\"",[223,2281,2282],{"class":256},": ",[223,2284,607],{"class":347},[223,2286,2287],{"class":256},"},   ",[223,2289,607],{"class":347},[223,2291,2252],{"class":256},[223,2293,2294],{"class":225,"line":365},[223,2295,777],{"class":256},[223,2297,2298,2301,2303],{"class":225,"line":439},[223,2299,2300],{"class":256},"    start ",[223,2302,279],{"class":252},[223,2304,2305],{"class":256}," time.perf_counter()\n",[223,2307,2308,2310,2312,2314,2316,2318,2321],{"class":225,"line":1238},[223,2309,1505],{"class":256},[223,2311,279],{"class":252},[223,2313,490],{"class":256},[223,2315,445],{"class":233},[223,2317,316],{"class":256},[223,2319,2320],{"class":252},"**",[223,2322,2323],{"class":256},"kwargs)\n",[223,2325,2326,2329,2331,2334],{"class":225,"line":1244},[223,2327,2328],{"class":256},"    total ",[223,2330,279],{"class":252},[223,2332,2333],{"class":347}," sum",[223,2335,2336],{"class":256},"(\n",[223,2338,2339,2342,2344,2346,2348,2351,2353,2355,2357,2360,2362,2364,2366,2368,2370,2372],{"class":225,"line":1250},[223,2340,2341],{"class":256},"        r[",[223,2343,534],{"class":347},[223,2345,1196],{"class":256},[223,2347,338],{"class":252},[223,2349,2350],{"class":256}," r ",[223,2352,344],{"class":252},[223,2354,503],{"class":256},[223,2356,506],{"class":233},[223,2358,2359],{"class":256},"].iter_rows(",[223,2361,529],{"class":528},[223,2363,279],{"class":252},[223,2365,534],{"class":347},[223,2367,316],{"class":256},[223,2369,602],{"class":528},[223,2371,279],{"class":252},[223,2373,2374],{"class":256},"values_only)\n",[223,2376,2377,2379,2382,2384,2386,2389,2391,2394,2396,2398,2400],{"class":225,"line":1260},[223,2378,1184],{"class":252},[223,2380,2381],{"class":256}," values_only ",[223,2383,1979],{"class":252},[223,2385,889],{"class":347},[223,2387,2388],{"class":256},"(r[",[223,2390,534],{"class":347},[223,2392,2393],{"class":256},"], (",[223,2395,895],{"class":347},[223,2397,316],{"class":256},[223,2399,900],{"class":347},[223,2401,2402],{"class":256},"))\n",[223,2404,2405,2408,2410,2412,2414],{"class":225,"line":1269},[223,2406,2407],{"class":256},"    ) ",[223,2409,886],{"class":252},[223,2411,2381],{"class":256},[223,2413,1545],{"class":252},[223,2415,1154],{"class":347},[223,2417,2418],{"class":225,"line":1274},[223,2419,2420],{"class":256},"    wb.close()\n",[223,2422,2423,2425,2427,2429,2431,2433,2436,2439,2441,2443,2446,2448,2451,2454,2456,2459],{"class":225,"line":1296},[223,2424,614],{"class":347},[223,2426,351],{"class":256},[223,2428,371],{"class":252},[223,2430,393],{"class":233},[223,2432,378],{"class":377},[223,2434,2435],{"class":256},"label",[223,2437,2438],{"class":252},":\u003C20",[223,2440,390],{"class":377},[223,2442,633],{"class":377},[223,2444,2445],{"class":256},"time.perf_counter() ",[223,2447,1190],{"class":252},[223,2449,2450],{"class":256}," start",[223,2452,2453],{"class":252},":6.3f",[223,2455,390],{"class":377},[223,2457,2458],{"class":233},"s\"",[223,2460,448],{"class":256},[10,2462,2463,2465,2466,2468,2469,2471,2472,31],{},[14,2464,2164],{}," streams the sheet instead of building the whole workbook in memory, and ",[14,2467,571],{}," skips constructing a ",[14,2470,460],{}," per cell. On a workbook of a few hundred thousand rows the pair is the difference between a job that fits in a container's memory limit and one that does not — the fuller treatment is in ",[27,2473,2475],{"href":2474},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fspeed-up-openpyxl-with-read-only-mode\u002F","speeding up openpyxl with read-only mode",[10,2477,2478,2479,2482,2483,2141,2486,2489,2490,2493,2494,2497,2498,2500],{},"Three further habits. ",[962,2480,2481],{},"Bound the columns",", not just the rows — a sheet with sixty columns where you need four wastes most of its parse on the rest. ",[962,2484,2485],{},"Close read-only workbooks explicitly",[14,2487,2488],{},"wb.close()","; they hold an open file handle that is not released by garbage collection alone, and a loop over hundreds of files will exhaust the descriptor limit. And ",[962,2491,2492],{},"do not iterate at all when pandas will do",": for a plain read-and-aggregate, ",[14,2495,2496],{},"pd.read_excel"," followed by a vectorised operation beats any Python-level loop by a wide margin. Reach for ",[14,2499,16],{}," when you need what pandas cannot see — the styling, the formulas, the coordinates — or when you are writing into an existing sheet.",[209,2502,2504],{"id":2503},"conclusion","Conclusion",[10,2506,2507,17,2509,2511,2512,2514,2515,2517,2518,2520],{},[14,2508,16],{},[14,2510,20],{}," are the two ways through a sheet, and the options matter more than the choice between them. Bound the range on both axes, pass ",[14,2513,571],{}," whenever you only need data, and open with ",[14,2516,2164],{}," for anything large. Never trust ",[14,2519,24],{}," as a row count — it reports the used range, which formatting alone can extend by tens of thousands of rows — so break on a blank key column instead. And key your rows by header name rather than position, so the day a column is inserted upstream your loop keeps working.",[209,2522,2524],{"id":2523},"frequently-asked-questions","Frequently asked questions",[10,2526,2527,2533,2534,2536],{},[962,2528,2529,2530,2532],{},"What does ",[14,2531,571],{}," actually change?","\nIt yields plain tuples of cell values instead of ",[14,2535,460],{}," objects. That skips constructing one object per cell, which is markedly faster and lighter — use it whenever you only need the data and not the styling or coordinates.",[10,2538,2539,2545,2547],{},[962,2540,2541,2542,2544],{},"Why does ",[14,2543,24],{}," report more rows than my data has?",[14,2546,24],{}," is the extent of the used range, not the count of populated rows. Formatting, a stray space, or a deleted-but-not-cleared row extends it. Break on a blank key column rather than trusting the number.",[10,2549,2550,2559,2561,2562,2564,2565,2567,2568,2570],{},[962,2551,2552,2553,2555,2556,2558],{},"Should I use ",[14,2554,799],{}," or ",[14,2557,16],{},"?",[14,2560,16],{},", because it accepts bounds and ",[14,2563,602],{},". ",[14,2566,799],{}," is a convenience alias for the whole sheet with no options, so it materialises ",[14,2569,460],{}," objects for every cell whether you need them or not.",[10,2572,2573,2576,2577,316,2579,316,2581,17,2583,2585,2586,2588,2589,2592],{},[962,2574,2575],{},"How do I iterate a specific range like B2 to D50?","\nPass ",[14,2578,529],{},[14,2580,24],{},[14,2582,673],{},[14,2584,676],{}," to ",[14,2587,16],{},", or index the sheet with a range string such as ",[14,2590,2591],{},"ws[\"B2:D50\"]",". Both yield tuples of cells row by row.",[10,2594,2595,2598,2599,2601,2602,2604,2605,2607],{},[962,2596,2597],{},"Why is my loop so slow on a large sheet?","\nYou are almost certainly building ",[14,2600,460],{}," objects you do not need. Open the workbook with ",[14,2603,2164],{}," and iterate with ",[14,2606,571],{},"; together they turn a whole-workbook parse into a streaming read.",[209,2609,2611],{"id":2610},"related","Related",[2613,2614,2615,2622,2628,2635,2641],"ul",{},[2616,2617,2618,2619,2621],"li",{},"Up to the parent: ",[27,2620,30],{"href":29}," — the wider openpyxl toolkit.",[2616,2623,2624,2627],{},[27,2625,2626],{"href":2039},"Insert and Delete Rows and Columns with openpyxl"," — changing the sheet's shape, which iteration cannot do safely.",[2616,2629,2630,2634],{},[27,2631,2633],{"href":2632},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fread-cell-value-from-excel-with-openpyxl\u002F","Read a Cell Value from Excel with openpyxl"," — single-cell access before you reach for a loop.",[2616,2636,2637,2640],{},[27,2638,2639],{"href":2474},"Speed up openpyxl with Read-Only Mode"," — the streaming mode that makes big iterations viable.",[2616,2642,2643,2646],{},[27,2644,2645],{"href":1797},"Read Formula Results with openpyxl data_only"," — getting values rather than formula text as you iterate.",[2648,2649,2650],"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 .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}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":219,"searchDepth":266,"depth":266,"links":2652},[2653,2654,2655,2656,2657,2658,2659,2660,2661,2662,2663],{"id":211,"depth":266,"text":212},{"id":451,"depth":266,"text":452},{"id":809,"depth":266,"text":810},{"id":970,"depth":266,"text":971},{"id":1352,"depth":266,"text":1353},{"id":1801,"depth":266,"text":1802},{"id":2043,"depth":266,"text":2044},{"id":2194,"depth":266,"text":2195},{"id":2503,"depth":266,"text":2504},{"id":2523,"depth":266,"text":2524},{"id":2610,"depth":266,"text":2611},"2026-08-15","Walk an Excel sheet cell by cell in Python — iter_rows and iter_cols, values_only, bounded ranges, the max_row trap, and building dicts keyed by header name.","md",[2668,2671,2674,2677,2679],{"q":2669,"a":2670},"What does values_only=True actually change?","It yields plain tuples of cell values instead of Cell objects. That skips constructing one object per cell, which is markedly faster and lighter — use it whenever you only need the data and not the styling or coordinates.",{"q":2672,"a":2673},"Why does max_row report more rows than my data has?","max_row is the extent of the used range, not the count of populated rows. Formatting, a stray space, or a deleted-but-not-cleared row extends it. Break on a blank key column rather than trusting the number.",{"q":2675,"a":2676},"Should I use ws.rows or iter_rows?","iter_rows, because it accepts bounds and values_only. ws.rows is a convenience alias for the whole sheet with no options, so it materialises Cell objects for every cell whether you need them or not.",{"q":2575,"a":2678},"Pass min_row, max_row, min_col and max_col to iter_rows, or index the sheet with a range string such as ws[\"B2:D50\"]. Both yield tuples of cells row by row.",{"q":2597,"a":2680},"You are almost certainly building Cell objects you do not need. Open the workbook with read_only=True and iterate with values_only=True; together they turn a whole-workbook parse into a streaming read.",{},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fiterate-over-rows-and-columns-with-openpyxl",{"title":2684,"description":2685},"openpyxl iter_rows and iter_cols: Iterate a Sheet","How to loop over Excel rows and columns with openpyxl: iter_rows, values_only for speed, bounding the range, why max_row overreports, and header-keyed row dicts.","iterate-over-rows-and-columns-with-openpyxl","getting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fiterate-over-rows-and-columns-with-openpyxl\u002Findex","how-to","EdnCXuSaqL1Vb4KDkXYJW3NyPv10FPSjBUlHhySMAEM",[2691,2694],{"title":2626,"path":2692,"stem":2693,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Finsert-and-delete-rows-and-columns-with-openpyxl","getting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Finsert-and-delete-rows-and-columns-with-openpyxl\u002Findex",{"title":2695,"path":2696,"stem":2697,"children":-1},"openpyxl: Append Data to an Existing Excel Sheet","\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fopenpyxl-append-data-to-existing-excel-sheet","getting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fopenpyxl-append-data-to-existing-excel-sheet\u002Findex",1786800028499]