[{"data":1,"prerenderedAt":1956},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Flightweight-python-excel-libraries-compared":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Flightweight-python-excel-libraries-compared":1926,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Flightweight-python-excel-libraries-compared":1934,"children:\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Flightweight-python-excel-libraries-compared":1939,"related:\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Flightweight-python-excel-libraries-compared":1940},{"id":4,"title":5,"body":6,"dateModified":1893,"datePublished":1893,"description":1894,"extension":1895,"faq":1896,"keyword":1905,"kind":1906,"meta":1907,"navigation":447,"path":1916,"seo":1917,"slug":1920,"stem":1921,"topics":1922,"type":1924,"__hash__":1925},"docs\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Flightweight-python-excel-libraries-compared\u002Findex.md","Lightweight Python Excel Libraries Compared",{"type":7,"value":8,"toc":1879},"minimark",[9,36,41,82,85,89,155,361,365,368,924,935,1031,1035,1038,1099,1148,1152,1159,1287,1317,1399,1403,1409,1707,1718,1722,1725,1729,1759,1766,1770,1807,1811,1814,1818,1824,1830,1836,1842,1846,1875],[10,11,12,13,17,18,21,22,25,26,29,30,35],"p",{},"pandas plus openpyxl is the default answer for Excel in Python, but it is not always the right one. A small command-line tool or AWS Lambda function that only needs to read a sheet does not want pandas, NumPy and their tens of megabytes; a script that must run on a locked-down machine may only be allowed pure-Python packages; and a job reading hundreds of large workbooks wants the fastest reader available. Several smaller libraries fill these gaps: ",[14,15,16],"strong",{},"pylightxl"," (pure Python, no dependencies), ",[14,19,20],{},"python-calamine"," (a Rust-based reader), and ",[14,23,24],{},"tablib"," and ",[14,27,28],{},"pyexcel"," (simple tabular interfaces over other engines). This page compares them on the same files — what they can do, what they pull in, how fast they read, what types they return — and reports two compatibility surprises found in testing. It belongs to ",[31,32,34],"a",{"href":33},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002F","Choosing a Python Excel Library",".",[37,38,40],"h2",{"id":39},"prerequisites","Prerequisites",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pylightxl python-calamine tablib[xlsx] pyexcel pyexcel-xlsx openpyxl\n","bash","",[49,50,51],"code",{"__ignoreMap":47},[52,53,56,60,64,67,70,73,76,79],"span",{"class":54,"line":55},"line",1,[52,57,59],{"class":58},"sMTad","pip",[52,61,63],{"class":62},"srMev"," install",[52,65,66],{"class":62}," pylightxl",[52,68,69],{"class":62}," python-calamine",[52,71,72],{"class":62}," tablib[xlsx]",[52,74,75],{"class":62}," pyexcel",[52,77,78],{"class":62}," pyexcel-xlsx",[52,80,81],{"class":62}," openpyxl\n",[10,83,84],{},"The test files are the same 50,000-row sheet — ID, date, region, quantity, price — written twice: once by openpyxl (as pandas also does by default) and once by XlsxWriter. The versions tested were pylightxl 1.61, python-calamine 0.8, tablib 3.10, pyexcel 0.7 with pyexcel-xlsx 0.6, and openpyxl 3.1 as the baseline; timings are from one run on an ordinary laptop and are for comparison only.",[37,86,88],{"id":87},"the-contenders","The contenders",[90,91,92,102,130,143],"ul",{},[93,94,95,97,98,101],"li",{},[14,96,16],{}," — a single pure-Python module with no dependencies. Reads and writes ",[49,99,100],{},".xlsx"," values (no formatting, no charts). Its selling point is that it can be copied into a project or installed where nothing else can.",[93,103,104,106,107,110,111,113,114,113,117,113,120,25,123,126,127,35],{},[14,105,20],{}," — Python bindings to the Rust ",[49,108,109],{},"calamine"," crate. Read-only, but reads ",[49,112,100],{},", ",[49,115,116],{},".xlsm",[49,118,119],{},".xlsb",[49,121,122],{},".xls",[49,124,125],{},".ods",", and is very fast. It ships as a compiled wheel with no Python dependencies; pandas uses it for ",[49,128,129],{},"engine=\"calamine\"",[93,131,132,134,135,137,138,142],{},[14,133,24],{}," — a library for tabular datasets with import and export to many formats (CSV, JSON, YAML, ",[49,136,100],{},"). For Excel it uses openpyxl under the hood, so it is light in ",[139,140,141],"em",{},"API",", not in dependencies.",[93,144,145,147,148,150,151,154],{},[14,146,28],{}," — a uniform API over plugins; ",[49,149,100],{}," support comes from the ",[49,152,153],{},"pyexcel-xlsx"," plugin, which also uses openpyxl.",[156,157,165,166,165,170,165,174,165,182,165,190,165,197,165,204,165,207,165,211,165,214,165,218,165,221,165,225,165,230,165,235,165,237,165,242,165,244,165,246,165,248,165,251,165,253,165,256,165,259,165,262,165,264,165,268,165,270,165,273,165,275,165,278,165,280,165,283,165,286,165,289,165,291,165,295,165,297,165,299,165,301,165,304,165,306,165,309,165,312,165,315,165,317,165,320,165,322,165,324,165,326,165,329,165,331,165,334,165,337,165,340,165,342,165,346,165,348,165,351,165,353,165,356,165,358],"svg",{"viewBox":158,"role":159,"ariaLabelledBy":160,"xmlns":163,"style":164},"4 0 752 242","img",[161,162],"lw-features-t","lw-features-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  ",[167,168,169],"title",{"id":161},"What each library can do",[171,172,173],"desc",{"id":162},"pylightxl reads and writes values with no dependencies. python-calamine only reads, but handles xlsx, xlsb, xls and ods quickly. tablib and pyexcel read and write xlsx through openpyxl. openpyxl reads and writes values, styles, charts and formulas.",[175,176],"rect",{"x":177,"y":178,"width":179,"height":180,"fill":181},"4","0","752","242","#ffffff",[175,183],{"x":184,"y":185,"width":186,"height":187,"rx":178,"fill":181,"stroke":188,"style":189},"20.0","16.0","137.6","40.0","var(--line,#cdd5e6)","stroke-width:1px",[175,191],{"x":192,"y":185,"width":193,"height":187,"rx":178,"fill":194,"stroke":195,"style":196},"157.6","145.6","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[198,199,203],"text",{"x":200,"y":201,"style":202},"230.4","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","read",[175,205],{"x":206,"y":185,"width":193,"height":187,"rx":178,"fill":194,"stroke":195,"style":196},"303.2",[198,208,210],{"x":209,"y":201,"style":202},"376.0","write",[175,212],{"x":213,"y":185,"width":193,"height":187,"rx":178,"fill":194,"stroke":195,"style":196},"448.8",[198,215,217],{"x":216,"y":201,"style":202},"521.6","formats",[175,219],{"x":220,"y":185,"width":193,"height":187,"rx":178,"fill":194,"stroke":195,"style":196},"594.4",[198,222,224],{"x":223,"y":201,"style":202},"667.2","depends on",[175,226],{"x":184,"y":227,"width":186,"height":228,"rx":178,"fill":229,"stroke":188,"style":189},"56.0","34.0","#e7ebef",[198,231,16],{"x":232,"y":233,"style":234},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[175,236],{"x":192,"y":227,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,238,241],{"x":200,"y":239,"style":240},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","✓ values",[175,243],{"x":206,"y":227,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,245,241],{"x":209,"y":239,"style":240},[175,247],{"x":213,"y":227,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,249,250],{"x":216,"y":239,"style":240},"xlsx",[175,252],{"x":220,"y":227,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,254,255],{"x":223,"y":239,"style":240},"nothing",[175,257],{"x":184,"y":258,"width":186,"height":228,"rx":178,"fill":229,"stroke":188,"style":189},"90.0",[198,260,20],{"x":232,"y":261,"style":234},"112.0",[175,263],{"x":192,"y":258,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,265,267],{"x":200,"y":266,"style":240},"111.0","✓ fast",[175,269],{"x":206,"y":258,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,271,272],{"x":209,"y":266,"style":240},"✗",[175,274],{"x":213,"y":258,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,276,277],{"x":216,"y":266,"style":240},"xlsx xlsb xls ods",[175,279],{"x":220,"y":258,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,281,282],{"x":223,"y":266,"style":240},"Rust wheel",[175,284],{"x":184,"y":285,"width":186,"height":228,"rx":178,"fill":229,"stroke":188,"style":189},"124.0",[198,287,24],{"x":232,"y":288,"style":234},"146.0",[175,290],{"x":192,"y":285,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,292,294],{"x":200,"y":293,"style":240},"145.0","✓",[175,296],{"x":206,"y":285,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,298,294],{"x":209,"y":293,"style":240},[175,300],{"x":213,"y":285,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,302,303],{"x":216,"y":293,"style":240},"xlsx + csv, json…",[175,305],{"x":220,"y":285,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,307,308],{"x":223,"y":293,"style":240},"openpyxl",[175,310],{"x":184,"y":311,"width":186,"height":228,"rx":178,"fill":229,"stroke":188,"style":189},"158.0",[198,313,28],{"x":232,"y":314,"style":234},"180.0",[175,316],{"x":192,"y":311,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,318,294],{"x":200,"y":319,"style":240},"179.0",[175,321],{"x":206,"y":311,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,323,294],{"x":209,"y":319,"style":240},[175,325],{"x":213,"y":311,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,327,328],{"x":216,"y":319,"style":240},"via plugins",[175,330],{"x":220,"y":311,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,332,333],{"x":223,"y":319,"style":240},"openpyxl (plugin)",[175,335],{"x":184,"y":336,"width":186,"height":228,"rx":178,"fill":229,"stroke":188,"style":189},"192.0",[198,338,308],{"x":232,"y":339,"style":234},"214.0",[175,341],{"x":192,"y":336,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,343,345],{"x":200,"y":344,"style":240},"213.0","✓ + styles",[175,347],{"x":206,"y":336,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,349,350],{"x":209,"y":344,"style":240},"✓ + charts",[175,352],{"x":213,"y":336,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,354,355],{"x":216,"y":344,"style":240},"xlsx, xlsm",[175,357],{"x":220,"y":336,"width":193,"height":228,"rx":178,"fill":181,"stroke":188,"style":189},[198,359,360],{"x":223,"y":344,"style":240},"et_xmlfile",[37,362,364],{"id":363},"step-1-read-the-same-file-with-each","Step 1 — Read the same file with each",[10,366,367],{},"Each library reads the sheet into rows; the helper times it and shows the first data row so the returned types are visible:",[42,369,373],{"className":370,"code":371,"language":372,"meta":47,"style":47},"language-python shiki shiki-themes github-light github-dark-high-contrast","import time\nimport pylightxl as xl\nimport pyexcel\nimport tablib\nfrom openpyxl import load_workbook\nfrom python_calamine import CalamineWorkbook\n\nreaders = {\n    \"pylightxl\": lambda f: list(xl.readxl(f).ws(\"Data\").rows),\n    \"python-calamine\": lambda f: CalamineWorkbook.from_path(f).get_sheet_by_name(\"Data\").to_python(),\n    \"pyexcel\": lambda f: pyexcel.get_array(file_name=f),\n    \"tablib\": lambda f: (lambda d: [tuple(d.headers)] + list(d))(tablib.Dataset().load(open(f, \"rb\"), format=\"xlsx\")),\n    \"openpyxl (read-only)\": lambda f: list(load_workbook(f, read_only=True)[\"Data\"].iter_rows(values_only=True)),\n}\n\ndef run(path):\n    results = {}\n    for name, read in readers.items():\n        start = time.perf_counter()\n        try:\n            rows = read(path)\n            results[name] = (round(time.perf_counter() - start, 2), len(rows), rows[1][:4])\n        except Exception as e:\n            results[name] = (None, 0, f\"{type(e).__name__}: {e}\")\n    return results\n\nxw = run(\"from_xlsxwriter.xlsx\")\nfor name, (secs, n, first) in xw.items():\n    print(f\"{name:22} {secs!s:>6}s  rows={n}  first={first}\")\n","python",[49,374,375,385,399,407,415,429,442,449,461,489,507,529,585,627,633,638,651,662,677,688,697,708,753,768,822,831,836,852,866],{"__ignoreMap":47},[52,376,377,381],{"class":54,"line":55},[52,378,380],{"class":379},"s-kum","import",[52,382,384],{"class":383},"skGVy"," time\n",[52,386,388,390,393,396],{"class":54,"line":387},2,[52,389,380],{"class":379},[52,391,392],{"class":383}," pylightxl ",[52,394,395],{"class":379},"as",[52,397,398],{"class":383}," xl\n",[52,400,402,404],{"class":54,"line":401},3,[52,403,380],{"class":379},[52,405,406],{"class":383}," pyexcel\n",[52,408,410,412],{"class":54,"line":409},4,[52,411,380],{"class":379},[52,413,414],{"class":383}," tablib\n",[52,416,418,421,424,426],{"class":54,"line":417},5,[52,419,420],{"class":379},"from",[52,422,423],{"class":383}," openpyxl ",[52,425,380],{"class":379},[52,427,428],{"class":383}," load_workbook\n",[52,430,432,434,437,439],{"class":54,"line":431},6,[52,433,420],{"class":379},[52,435,436],{"class":383}," python_calamine ",[52,438,380],{"class":379},[52,440,441],{"class":383}," CalamineWorkbook\n",[52,443,445],{"class":54,"line":444},7,[52,446,448],{"emptyLinePlaceholder":447},true,"\n",[52,450,452,455,458],{"class":54,"line":451},8,[52,453,454],{"class":383},"readers ",[52,456,457],{"class":379},"=",[52,459,460],{"class":383}," {\n",[52,462,464,467,470,473,476,480,483,486],{"class":54,"line":463},9,[52,465,466],{"class":62},"    \"pylightxl\"",[52,468,469],{"class":383},": ",[52,471,472],{"class":379},"lambda",[52,474,475],{"class":383}," f: ",[52,477,479],{"class":478},"sP0c6","list",[52,481,482],{"class":383},"(xl.readxl(f).ws(",[52,484,485],{"class":62},"\"Data\"",[52,487,488],{"class":383},").rows),\n",[52,490,492,495,497,499,502,504],{"class":54,"line":491},10,[52,493,494],{"class":62},"    \"python-calamine\"",[52,496,469],{"class":383},[52,498,472],{"class":379},[52,500,501],{"class":383}," f: CalamineWorkbook.from_path(f).get_sheet_by_name(",[52,503,485],{"class":62},[52,505,506],{"class":383},").to_python(),\n",[52,508,510,513,515,517,520,524,526],{"class":54,"line":509},11,[52,511,512],{"class":62},"    \"pyexcel\"",[52,514,469],{"class":383},[52,516,472],{"class":379},[52,518,519],{"class":383}," f: pyexcel.get_array(",[52,521,523],{"class":522},"sa561","file_name",[52,525,457],{"class":379},[52,527,528],{"class":383},"f),\n",[52,530,532,535,537,539,542,544,547,550,553,556,559,562,565,568,571,574,577,579,582],{"class":54,"line":531},12,[52,533,534],{"class":62},"    \"tablib\"",[52,536,469],{"class":383},[52,538,472],{"class":379},[52,540,541],{"class":383}," f: (",[52,543,472],{"class":379},[52,545,546],{"class":383}," d: [",[52,548,549],{"class":478},"tuple",[52,551,552],{"class":383},"(d.headers)] ",[52,554,555],{"class":379},"+",[52,557,558],{"class":478}," list",[52,560,561],{"class":383},"(d))(tablib.Dataset().load(",[52,563,564],{"class":478},"open",[52,566,567],{"class":383},"(f, ",[52,569,570],{"class":62},"\"rb\"",[52,572,573],{"class":383},"), ",[52,575,576],{"class":522},"format",[52,578,457],{"class":379},[52,580,581],{"class":62},"\"xlsx\"",[52,583,584],{"class":383},")),\n",[52,586,588,591,593,595,597,599,602,605,607,610,613,615,618,621,623,625],{"class":54,"line":587},13,[52,589,590],{"class":62},"    \"openpyxl (read-only)\"",[52,592,469],{"class":383},[52,594,472],{"class":379},[52,596,475],{"class":383},[52,598,479],{"class":478},[52,600,601],{"class":383},"(load_workbook(f, ",[52,603,604],{"class":522},"read_only",[52,606,457],{"class":379},[52,608,609],{"class":478},"True",[52,611,612],{"class":383},")[",[52,614,485],{"class":62},[52,616,617],{"class":383},"].iter_rows(",[52,619,620],{"class":522},"values_only",[52,622,457],{"class":379},[52,624,609],{"class":478},[52,626,584],{"class":383},[52,628,630],{"class":54,"line":629},14,[52,631,632],{"class":383},"}\n",[52,634,636],{"class":54,"line":635},15,[52,637,448],{"emptyLinePlaceholder":447},[52,639,641,644,648],{"class":54,"line":640},16,[52,642,643],{"class":379},"def",[52,645,647],{"class":646},"s_Opv"," run",[52,649,650],{"class":383},"(path):\n",[52,652,654,657,659],{"class":54,"line":653},17,[52,655,656],{"class":383},"    results ",[52,658,457],{"class":379},[52,660,661],{"class":383}," {}\n",[52,663,665,668,671,674],{"class":54,"line":664},18,[52,666,667],{"class":379},"    for",[52,669,670],{"class":383}," name, read ",[52,672,673],{"class":379},"in",[52,675,676],{"class":383}," readers.items():\n",[52,678,680,683,685],{"class":54,"line":679},19,[52,681,682],{"class":383},"        start ",[52,684,457],{"class":379},[52,686,687],{"class":383}," time.perf_counter()\n",[52,689,691,694],{"class":54,"line":690},20,[52,692,693],{"class":379},"        try",[52,695,696],{"class":383},":\n",[52,698,700,703,705],{"class":54,"line":699},21,[52,701,702],{"class":383},"            rows ",[52,704,457],{"class":379},[52,706,707],{"class":383}," read(path)\n",[52,709,711,714,716,719,722,725,728,731,734,736,739,742,745,748,750],{"class":54,"line":710},22,[52,712,713],{"class":383},"            results[name] ",[52,715,457],{"class":379},[52,717,718],{"class":383}," (",[52,720,721],{"class":478},"round",[52,723,724],{"class":383},"(time.perf_counter() ",[52,726,727],{"class":379},"-",[52,729,730],{"class":383}," start, ",[52,732,733],{"class":478},"2",[52,735,573],{"class":383},[52,737,738],{"class":478},"len",[52,740,741],{"class":383},"(rows), rows[",[52,743,744],{"class":478},"1",[52,746,747],{"class":383},"][:",[52,749,177],{"class":478},[52,751,752],{"class":383},"])\n",[52,754,756,759,762,765],{"class":54,"line":755},23,[52,757,758],{"class":379},"        except",[52,760,761],{"class":478}," Exception",[52,763,764],{"class":379}," as",[52,766,767],{"class":383}," e:\n",[52,769,771,773,775,777,780,782,784,786,789,792,796,799,802,805,808,810,812,815,817,819],{"class":54,"line":770},24,[52,772,713],{"class":383},[52,774,457],{"class":379},[52,776,718],{"class":383},[52,778,779],{"class":478},"None",[52,781,113],{"class":383},[52,783,178],{"class":478},[52,785,113],{"class":383},[52,787,788],{"class":379},"f",[52,790,791],{"class":62},"\"",[52,793,795],{"class":794},"sSjpA","{",[52,797,798],{"class":478},"type",[52,800,801],{"class":383},"(e).",[52,803,804],{"class":478},"__name__",[52,806,807],{"class":794},"}",[52,809,469],{"class":62},[52,811,795],{"class":794},[52,813,814],{"class":383},"e",[52,816,807],{"class":794},[52,818,791],{"class":62},[52,820,821],{"class":383},")\n",[52,823,825,828],{"class":54,"line":824},25,[52,826,827],{"class":379},"    return",[52,829,830],{"class":383}," results\n",[52,832,834],{"class":54,"line":833},26,[52,835,448],{"emptyLinePlaceholder":447},[52,837,839,842,844,847,850],{"class":54,"line":838},27,[52,840,841],{"class":383},"xw ",[52,843,457],{"class":379},[52,845,846],{"class":383}," run(",[52,848,849],{"class":62},"\"from_xlsxwriter.xlsx\"",[52,851,821],{"class":383},[52,853,855,858,861,863],{"class":54,"line":854},28,[52,856,857],{"class":379},"for",[52,859,860],{"class":383}," name, (secs, n, first) ",[52,862,673],{"class":379},[52,864,865],{"class":383}," xw.items():\n",[52,867,869,872,875,877,879,881,884,887,889,892,895,898,900,903,905,908,910,913,915,918,920,922],{"class":54,"line":868},29,[52,870,871],{"class":478},"    print",[52,873,874],{"class":383},"(",[52,876,788],{"class":379},[52,878,791],{"class":62},[52,880,795],{"class":794},[52,882,883],{"class":383},"name",[52,885,886],{"class":379},":22",[52,888,807],{"class":794},[52,890,891],{"class":794}," {",[52,893,894],{"class":383},"secs",[52,896,897],{"class":379},"!s:>6",[52,899,807],{"class":794},[52,901,902],{"class":62},"s  rows=",[52,904,795],{"class":794},[52,906,907],{"class":383},"n",[52,909,807],{"class":794},[52,911,912],{"class":62},"  first=",[52,914,795],{"class":794},[52,916,917],{"class":383},"first",[52,919,807],{"class":794},[52,921,791],{"class":62},[52,923,821],{"class":383},[10,925,926,927,930,931,934],{},"All five read the XlsxWriter-made file. python-calamine is in a class of its own — around a tenth of a second where the others take three quarters of a second to a second and a half — because its parser is compiled Rust. openpyxl in read-only mode is the fastest of the pure-Python readers; tablib and pyexcel are slower than openpyxl because they ",[139,928,929],{},"use"," openpyxl and then convert its output. pylightxl, despite being tiny, is not fast: it parses with the standard library's XML module and builds all rows in memory. tablib keeps the header row separately in ",[49,932,933],{},"headers",", so the helper puts it back in front to make the row counts comparable.",[156,936,165,942,165,945,165,948,165,952,165,957,165,962,165,969,165,974,165,976,165,980,165,983,165,988,165,991,165,994,165,997,165,1002,165,1005,165,1008,165,1011,165,1015,165,1018,165,1021,165,1026],{"viewBox":937,"role":159,"ariaLabelledBy":938,"xmlns":163,"style":941},"4 0 741 220",[939,940],"lw-speed-t","lw-speed-d","width:100%;max-width:741px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[167,943,944],{"id":939},"Seconds to read 50,000 rows (one run, XlsxWriter file)",[171,946,947],{"id":940},"python-calamine read the file in about 0.1 seconds. openpyxl in read-only mode took about 0.8 seconds, tablib about 1.2, pyexcel about 1.4 and pylightxl about 1.5 seconds.",[175,949],{"x":177,"y":178,"width":950,"height":951,"fill":181},"741","220",[175,953],{"x":184,"y":185,"width":954,"height":955,"rx":956,"fill":229,"stroke":188,"style":189},"153.6","28.0","7",[198,958,20],{"x":959,"y":960,"style":961},"96.8","34.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[175,963],{"x":964,"y":185,"width":965,"height":955,"rx":956,"fill":966,"stroke":967,"style":968},"183.6","33.3","#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[198,970,973],{"x":971,"y":960,"style":972},"224.9","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","≈ 0.1 s",[175,975],{"x":184,"y":227,"width":954,"height":955,"rx":956,"fill":229,"stroke":188,"style":189},[198,977,979],{"x":959,"y":978,"style":961},"74.5","openpyxl read-only",[175,981],{"x":964,"y":227,"width":982,"height":955,"rx":956,"fill":194,"stroke":195,"style":968},"266.1",[198,984,987],{"x":985,"y":978,"style":986},"457.7","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start","≈ 0.8 s",[175,989],{"x":184,"y":990,"width":954,"height":955,"rx":956,"fill":229,"stroke":188,"style":189},"96.0",[198,992,24],{"x":959,"y":993,"style":961},"114.5",[175,995],{"x":964,"y":990,"width":996,"height":955,"rx":956,"fill":229,"stroke":188,"style":968},"399.1",[198,998,1001],{"x":999,"y":993,"style":1000},"590.7","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start","≈ 1.2 s",[175,1003],{"x":184,"y":1004,"width":954,"height":955,"rx":956,"fill":229,"stroke":188,"style":189},"136.0",[198,1006,28],{"x":959,"y":1007,"style":961},"154.5",[175,1009],{"x":964,"y":1004,"width":1010,"height":955,"rx":956,"fill":229,"stroke":188,"style":968},"465.6",[198,1012,1014],{"x":1013,"y":1007,"style":1000},"657.3","≈ 1.4 s",[175,1016],{"x":184,"y":1017,"width":954,"height":955,"rx":956,"fill":229,"stroke":188,"style":189},"176.0",[198,1019,16],{"x":959,"y":1020,"style":961},"194.5",[175,1022],{"x":964,"y":1017,"width":1023,"height":955,"rx":956,"fill":1024,"stroke":1025,"style":968},"498.9","#fdefd8","var(--gold,#b4740a)",[198,1027,1030],{"x":1028,"y":1020,"style":1029},"690.5","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","≈ 1.5 s",[37,1032,1034],{"id":1033},"step-2-look-at-the-types-you-get-back","Step 2 — Look at the types you get back",[10,1036,1037],{},"Speed is not the only difference. The first data row printed above shows each library's idea of a number and a date:",[42,1039,1041],{"className":370,"code":1040,"language":372,"meta":47,"style":47},"for name, (_, _, first) in xw.items():\n    print(f\"{name:22}\", [type(v).__name__ for v in first], first[1])\n",[49,1042,1043,1054],{"__ignoreMap":47},[52,1044,1045,1047,1050,1052],{"class":54,"line":55},[52,1046,857],{"class":379},[52,1048,1049],{"class":383}," name, (_, _, first) ",[52,1051,673],{"class":379},[52,1053,865],{"class":383},[52,1055,1056,1058,1060,1062,1064,1066,1068,1070,1072,1074,1077,1079,1082,1084,1087,1090,1092,1095,1097],{"class":54,"line":387},[52,1057,871],{"class":478},[52,1059,874],{"class":383},[52,1061,788],{"class":379},[52,1063,791],{"class":62},[52,1065,795],{"class":794},[52,1067,883],{"class":383},[52,1069,886],{"class":379},[52,1071,807],{"class":794},[52,1073,791],{"class":62},[52,1075,1076],{"class":383},", [",[52,1078,798],{"class":478},[52,1080,1081],{"class":383},"(v).",[52,1083,804],{"class":478},[52,1085,1086],{"class":379}," for",[52,1088,1089],{"class":383}," v ",[52,1091,673],{"class":379},[52,1093,1094],{"class":383}," first], first[",[52,1096,744],{"class":478},[52,1098,752],{"class":383},[10,1100,1101,1102,1104,1105,1108,1109,1112,1113,1115,1116,1119,1120,1123,1124,1127,1128,113,1130,25,1132,1134,1135,1138,1139,1142,1143,1147],{},"The differences matter as soon as values are used. ",[14,1103,16],{}," returns the date as the ",[139,1106,1107],{},"string"," ",[49,1110,1111],{},"'2026\u002F01\u002F01'"," — it applies the cell's date format itself and gives you text, so date arithmetic needs parsing first. ",[14,1114,20],{}," returns a proper ",[49,1117,1118],{},"datetime.date",", but returns whole numbers as floats (",[49,1121,1122],{},"0.0","), because Excel stores all numbers as floating point and calamine does not guess which were meant as integers; IDs and quantities need converting with ",[49,1125,1126],{},"int()"," where that matters. ",[14,1129,308],{},[14,1131,24],{},[14,1133,28],{}," return ",[49,1136,1137],{},"datetime.datetime"," for dates and ",[49,1140,1141],{},"int"," for whole numbers. None of these is wrong, but swapping one library for another without checking types is a classic source of subtle bugs — see ",[31,1144,1146],{"href":1145},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fhow-pandas-data-types-are-written-to-excel-cells\u002F","How pandas Data Types Are Written to Excel Cells"," for the writing side of the same question.",[37,1149,1151],{"id":1150},"step-3-a-compatibility-surprise","Step 3 — A compatibility surprise",[10,1153,1154,1155,1158],{},"Now read the copy written by openpyxl — the format most Python-generated workbooks, including those from ",[49,1156,1157],{},"pandas.DataFrame.to_excel",", are in:",[42,1160,1162],{"className":370,"code":1161,"language":372,"meta":47,"style":47},"op = run(\"from_openpyxl.xlsx\")\nfor name, (secs, n, first) in op.items():\n    print(f\"{name:22} {'failed' if secs is None else f'{secs}s':>7}  rows={n}  first={first if n else first[:70]}\")\n",[49,1163,1164,1178,1189],{"__ignoreMap":47},[52,1165,1166,1169,1171,1173,1176],{"class":54,"line":55},[52,1167,1168],{"class":383},"op ",[52,1170,457],{"class":379},[52,1172,846],{"class":383},[52,1174,1175],{"class":62},"\"from_openpyxl.xlsx\"",[52,1177,821],{"class":383},[52,1179,1180,1182,1184,1186],{"class":54,"line":387},[52,1181,857],{"class":379},[52,1183,860],{"class":383},[52,1185,673],{"class":379},[52,1187,1188],{"class":383}," op.items():\n",[52,1190,1191,1193,1195,1197,1199,1201,1203,1205,1207,1209,1212,1215,1218,1221,1224,1227,1230,1233,1235,1237,1239,1242,1245,1247,1250,1252,1254,1256,1258,1260,1263,1266,1269,1272,1275,1278,1281,1283,1285],{"class":54,"line":401},[52,1192,871],{"class":478},[52,1194,874],{"class":383},[52,1196,788],{"class":379},[52,1198,791],{"class":62},[52,1200,795],{"class":794},[52,1202,883],{"class":383},[52,1204,886],{"class":379},[52,1206,807],{"class":794},[52,1208,891],{"class":794},[52,1210,1211],{"class":62},"'failed'",[52,1213,1214],{"class":379}," if",[52,1216,1217],{"class":383}," secs ",[52,1219,1220],{"class":379},"is",[52,1222,1223],{"class":478}," None",[52,1225,1226],{"class":379}," else",[52,1228,1229],{"class":379}," f",[52,1231,1232],{"class":62},"'",[52,1234,795],{"class":794},[52,1236,894],{"class":383},[52,1238,807],{"class":794},[52,1240,1241],{"class":62},"s'",[52,1243,1244],{"class":379},":>7",[52,1246,807],{"class":794},[52,1248,1249],{"class":62},"  rows=",[52,1251,795],{"class":794},[52,1253,907],{"class":383},[52,1255,807],{"class":794},[52,1257,912],{"class":62},[52,1259,795],{"class":794},[52,1261,1262],{"class":383},"first ",[52,1264,1265],{"class":379},"if",[52,1267,1268],{"class":383}," n ",[52,1270,1271],{"class":379},"else",[52,1273,1274],{"class":383}," first[:",[52,1276,1277],{"class":478},"70",[52,1279,1280],{"class":383},"]",[52,1282,807],{"class":794},[52,1284,791],{"class":62},[52,1286,821],{"class":383},[10,1288,1289,1290,1293,1294,1297,1298,1301,1302,1305,1306,1309,1310,1312,1313,1316],{},"Every library reads it except ",[14,1291,1292],{},"pylightxl 1.61",", which fails with a ",[49,1295,1296],{},"TypeError"," before reading any cells. The cause is in how it looks up each sheet's relationship ID: it only recognises the ",[49,1299,1300],{},"r:"," namespace when it is declared on the root element of ",[49,1303,1304],{},"workbook.xml",", and openpyxl declares it on each ",[49,1307,1308],{},"\u003Csheet>"," element instead — perfectly valid XML that pylightxl's lookup misses. Files saved by Excel and by XlsxWriter declare it on the root, so they read fine. The lesson is broader than one bug: small libraries are tested against fewer producers of ",[49,1311,100],{}," files, so test any lightweight reader against files from ",[139,1314,1315],{},"every"," source your job will see before relying on it.",[156,1318,165,1323,165,1326,165,1329,165,1332,165,1338,165,1349,165,1354,165,1359,165,1363,165,1367,165,1371,165,1374,165,1381,165,1386,165,1390,165,1393,165,1396],{"viewBox":1319,"role":159,"ariaLabelledBy":1320,"xmlns":163,"style":164},"4 0 752 207",[1321,1322],"lw-surprise-t","lw-surprise-d",[167,1324,1325],{"id":1321},"The same sheet, two producers",[171,1327,1328],{"id":1322},"pylightxl read the workbook written by XlsxWriter but failed on the identical data written by openpyxl, because openpyxl declares the relationships namespace on each sheet element rather than on the root. python-calamine, tablib, pyexcel and openpyxl read both.",[175,1330],{"x":177,"y":178,"width":179,"height":1331,"fill":181},"207",[175,1333],{"x":184,"y":185,"width":1334,"height":1335,"rx":1336,"fill":966,"stroke":967,"style":1337},"351.0","175.0","14","stroke-width:2px",[1339,1340,1342],"g",{"transform":1341},"translate(182.5,29.0) scale(1.083)",[1343,1344],"path",{"d":1345,"fill":1346,"stroke":1347,"style":1348},"M5 12.5l4.5 4.5L19 7","none","var(--teal-ink,#0b6157)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[198,1350,1353],{"x":1351,"y":233,"style":1352},"195.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","file from XlsxWriter",[54,1355],{"x1":1356,"y1":1357,"x2":1358,"y2":1357,"stroke":967,"style":189},"36.0","91.0","355.0",[198,1360,1362],{"x":1351,"y":266,"style":1361},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","namespace declared on root",[198,1364,1366],{"x":1351,"y":1365,"style":1361},"137.0","all five libraries read it",[198,1368,1370],{"x":1351,"y":1369,"style":1361},"163.0","pylightxl dates come back as text",[175,1372],{"x":1373,"y":185,"width":1334,"height":1335,"rx":1336,"fill":1024,"stroke":1025,"style":1337},"389.0",[1339,1375,1377],{"transform":1376},"translate(551.5,29.0) scale(1.083)",[1343,1378],{"d":1379,"fill":1346,"stroke":1380,"style":1348},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[198,1382,1385],{"x":1383,"y":233,"style":1384},"564.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","file from openpyxl \u002F pandas",[54,1387],{"x1":1388,"y1":1357,"x2":1389,"y2":1357,"stroke":1025,"style":189},"405.0","724.0",[198,1391,1392],{"x":1383,"y":266,"style":1361},"namespace declared per sheet",[198,1394,1395],{"x":1383,"y":1365,"style":1361},"pylightxl 1.61: TypeError",[198,1397,1398],{"x":1383,"y":1369,"style":1361},"the other four read it",[37,1400,1402],{"id":1401},"step-4-writing","Step 4 — Writing",[10,1404,1405,1406,1408],{},"For writing, the field narrows. python-calamine cannot write at all. tablib and pyexcel write ",[49,1407,100],{}," through openpyxl. pylightxl writes values with no dependencies:",[42,1410,1412],{"className":370,"code":1411,"language":372,"meta":47,"style":47},"db = xl.Database()\ndb.add_ws(ws=\"Summary\")\nfor r, row in enumerate([[\"Region\", \"Qty\"], [\"North\", 120], [\"South\", 85]], start=1):\n    for c, value in enumerate(row, start=1):\n        db.ws(\"Summary\").update_index(row=r, col=c, val=value)\nxl.writexl(db=db, fn=\"summary_pylightxl.xlsx\")\n\ndata = tablib.Dataset([\"North\", 120], [\"South\", 85], headers=[\"Region\", \"Qty\"])\nwith open(\"summary_tablib.xlsx\", \"wb\") as fh:\n    fh.write(data.export(\"xlsx\"))\n\nfor f in (\"summary_pylightxl.xlsx\", \"summary_tablib.xlsx\"):\n    print(f, [[c.value for c in row] for row in load_workbook(f).active.iter_rows()])\n",[49,1413,1414,1424,1439,1496,1518,1552,1575,1579,1621,1647,1657,1661,1680],{"__ignoreMap":47},[52,1415,1416,1419,1421],{"class":54,"line":55},[52,1417,1418],{"class":383},"db ",[52,1420,457],{"class":379},[52,1422,1423],{"class":383}," xl.Database()\n",[52,1425,1426,1429,1432,1434,1437],{"class":54,"line":387},[52,1427,1428],{"class":383},"db.add_ws(",[52,1430,1431],{"class":522},"ws",[52,1433,457],{"class":379},[52,1435,1436],{"class":62},"\"Summary\"",[52,1438,821],{"class":383},[52,1440,1441,1443,1446,1448,1451,1454,1457,1459,1462,1465,1468,1470,1473,1475,1478,1480,1483,1486,1489,1491,1493],{"class":54,"line":401},[52,1442,857],{"class":379},[52,1444,1445],{"class":383}," r, row ",[52,1447,673],{"class":379},[52,1449,1450],{"class":478}," enumerate",[52,1452,1453],{"class":383},"([[",[52,1455,1456],{"class":62},"\"Region\"",[52,1458,113],{"class":383},[52,1460,1461],{"class":62},"\"Qty\"",[52,1463,1464],{"class":383},"], [",[52,1466,1467],{"class":62},"\"North\"",[52,1469,113],{"class":383},[52,1471,1472],{"class":478},"120",[52,1474,1464],{"class":383},[52,1476,1477],{"class":62},"\"South\"",[52,1479,113],{"class":383},[52,1481,1482],{"class":478},"85",[52,1484,1485],{"class":383},"]], ",[52,1487,1488],{"class":522},"start",[52,1490,457],{"class":379},[52,1492,744],{"class":478},[52,1494,1495],{"class":383},"):\n",[52,1497,1498,1500,1503,1505,1507,1510,1512,1514,1516],{"class":54,"line":409},[52,1499,667],{"class":379},[52,1501,1502],{"class":383}," c, value ",[52,1504,673],{"class":379},[52,1506,1450],{"class":478},[52,1508,1509],{"class":383},"(row, ",[52,1511,1488],{"class":522},[52,1513,457],{"class":379},[52,1515,744],{"class":478},[52,1517,1495],{"class":383},[52,1519,1520,1523,1525,1528,1531,1533,1536,1539,1541,1544,1547,1549],{"class":54,"line":417},[52,1521,1522],{"class":383},"        db.ws(",[52,1524,1436],{"class":62},[52,1526,1527],{"class":383},").update_index(",[52,1529,1530],{"class":522},"row",[52,1532,457],{"class":379},[52,1534,1535],{"class":383},"r, ",[52,1537,1538],{"class":522},"col",[52,1540,457],{"class":379},[52,1542,1543],{"class":383},"c, ",[52,1545,1546],{"class":522},"val",[52,1548,457],{"class":379},[52,1550,1551],{"class":383},"value)\n",[52,1553,1554,1557,1560,1562,1565,1568,1570,1573],{"class":54,"line":431},[52,1555,1556],{"class":383},"xl.writexl(",[52,1558,1559],{"class":522},"db",[52,1561,457],{"class":379},[52,1563,1564],{"class":383},"db, ",[52,1566,1567],{"class":522},"fn",[52,1569,457],{"class":379},[52,1571,1572],{"class":62},"\"summary_pylightxl.xlsx\"",[52,1574,821],{"class":383},[52,1576,1577],{"class":54,"line":444},[52,1578,448],{"emptyLinePlaceholder":447},[52,1580,1581,1584,1586,1589,1591,1593,1595,1597,1599,1601,1603,1606,1608,1610,1613,1615,1617,1619],{"class":54,"line":451},[52,1582,1583],{"class":383},"data ",[52,1585,457],{"class":379},[52,1587,1588],{"class":383}," tablib.Dataset([",[52,1590,1467],{"class":62},[52,1592,113],{"class":383},[52,1594,1472],{"class":478},[52,1596,1464],{"class":383},[52,1598,1477],{"class":62},[52,1600,113],{"class":383},[52,1602,1482],{"class":478},[52,1604,1605],{"class":383},"], ",[52,1607,933],{"class":522},[52,1609,457],{"class":379},[52,1611,1612],{"class":383},"[",[52,1614,1456],{"class":62},[52,1616,113],{"class":383},[52,1618,1461],{"class":62},[52,1620,752],{"class":383},[52,1622,1623,1626,1629,1631,1634,1636,1639,1642,1644],{"class":54,"line":463},[52,1624,1625],{"class":379},"with",[52,1627,1628],{"class":478}," open",[52,1630,874],{"class":383},[52,1632,1633],{"class":62},"\"summary_tablib.xlsx\"",[52,1635,113],{"class":383},[52,1637,1638],{"class":62},"\"wb\"",[52,1640,1641],{"class":383},") ",[52,1643,395],{"class":379},[52,1645,1646],{"class":383}," fh:\n",[52,1648,1649,1652,1654],{"class":54,"line":491},[52,1650,1651],{"class":383},"    fh.write(data.export(",[52,1653,581],{"class":62},[52,1655,1656],{"class":383},"))\n",[52,1658,1659],{"class":54,"line":509},[52,1660,448],{"emptyLinePlaceholder":447},[52,1662,1663,1665,1668,1670,1672,1674,1676,1678],{"class":54,"line":531},[52,1664,857],{"class":379},[52,1666,1667],{"class":383}," f ",[52,1669,673],{"class":379},[52,1671,718],{"class":383},[52,1673,1572],{"class":62},[52,1675,113],{"class":383},[52,1677,1633],{"class":62},[52,1679,1495],{"class":383},[52,1681,1682,1684,1687,1689,1692,1694,1697,1699,1702,1704],{"class":54,"line":587},[52,1683,871],{"class":478},[52,1685,1686],{"class":383},"(f, [[c.value ",[52,1688,857],{"class":379},[52,1690,1691],{"class":383}," c ",[52,1693,673],{"class":379},[52,1695,1696],{"class":383}," row] ",[52,1698,857],{"class":379},[52,1700,1701],{"class":383}," row ",[52,1703,673],{"class":379},[52,1705,1706],{"class":383}," load_workbook(f).active.iter_rows()])\n",[10,1708,1709,1710,1713,1714,35],{},"Both produce a valid workbook that openpyxl reads back. Neither offers formatting worth the name — no number formats, column widths or fonts in pylightxl; tablib makes the header bold and little more. For anything that will be read by people rather than programs, XlsxWriter (a single pure-Python package with no dependencies of its own) is the better lightweight ",[139,1711,1712],{},"writer",": it is fast, small and supports formatting, charts and tables. It is covered in ",[31,1715,1717],{"href":1716},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter\u002F","Write a Formatted Excel Report with XlsxWriter",[37,1719,1721],{"id":1720},"install-footprint","Install footprint",[10,1723,1724],{},"On disk, after installation, the packages compared here occupied roughly: pylightxl 0.2 MB, tablib 0.3 MB (plus openpyxl), pyexcel with its io layer and xlsx plugin about 1 MB (plus openpyxl), python-calamine 2.2 MB (a compiled binary), openpyxl 2.8 MB, and XlsxWriter 2 MB. pandas with NumPy is well over 50 MB. For a Lambda function or a container image, the gap between \"openpyxl or calamine\" and \"pandas\" is what matters; between the small libraries, it rarely does.",[37,1726,1728],{"id":1727},"which-to-use","Which to use",[90,1730,1731,1741,1747,1753],{},[93,1732,1733,1736,1737,1740],{},[14,1734,1735],{},"Fastest reading, any Excel format:"," python-calamine — directly, or through ",[49,1738,1739],{},"pandas.read_excel(engine=\"calamine\")"," when you have pandas anyway. Convert floats back to integers where needed.",[93,1742,1743,1746],{},[14,1744,1745],{},"No dependencies allowed, simple values:"," pylightxl — but test it against your actual files, and parse its date strings.",[93,1748,1749,1752],{},[14,1750,1751],{},"Many formats through one small API:"," tablib, accepting openpyxl as a dependency.",[93,1754,1755,1758],{},[14,1756,1757],{},"Reading and writing with formatting, still lightweight:"," openpyxl to read, XlsxWriter to write.",[10,1760,1761,1762,35],{},"More detailed timing across engines is in ",[31,1763,1765],{"href":1764},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fbenchmark-python-excel-read-and-write-speed\u002F","Benchmark Python Excel Read and Write Speed",[37,1767,1769],{"id":1768},"common-pitfalls","Common pitfalls",[90,1771,1772,1780,1786,1795,1801],{},[93,1773,1774,1779],{},[14,1775,1776,1777,35],{},"Assuming a small library reads every ",[49,1778,100],{}," Test with files from Excel, openpyxl, XlsxWriter and any other producer you receive.",[93,1781,1782,1785],{},[14,1783,1784],{},"Ignoring returned types."," Dates as text, integers as floats — check before computing.",[93,1787,1788,1791,1792,1794],{},[14,1789,1790],{},"Expecting tablib or pyexcel to avoid openpyxl."," For ",[49,1793,100],{}," they use it underneath.",[93,1796,1797,1800],{},[14,1798,1799],{},"Using calamine to write."," It is read-only.",[93,1802,1803,1806],{},[14,1804,1805],{},"Choosing a reader by install size alone."," Speed and correctness on your files matter more.",[37,1808,1810],{"id":1809},"conclusion","Conclusion",[10,1812,1813],{},"The lightweight Excel libraries each have a niche. python-calamine is by far the fastest reader and handles every Excel format, returning floats for all numbers. pylightxl needs nothing but Python and reads and writes plain values, though it returns dates as text and, in version 1.61, could not read files written by openpyxl. tablib and pyexcel offer simple tabular APIs on top of openpyxl. For most jobs that want to stay small, python-calamine for reading and XlsxWriter for writing is the strongest pair — after testing against the files you actually receive.",[37,1815,1817],{"id":1816},"frequently-asked-questions","Frequently asked questions",[10,1819,1820,1823],{},[14,1821,1822],{},"What is the fastest Python library for reading Excel files?","\npython-calamine, which wraps the Rust calamine crate. It read a 50,000-row sheet in about a tenth of a second in our test, roughly eight times faster than openpyxl in read-only mode. pandas can use it with engine=\"calamine\".",[10,1825,1826,1829],{},[14,1827,1828],{},"Is there an Excel library for Python with no dependencies?","\npylightxl is a single pure-Python module with no dependencies that reads and writes cell values. XlsxWriter also has no dependencies but only writes.",[10,1831,1832,1835],{},[14,1833,1834],{},"Why can't pylightxl read my Excel file?","\npylightxl 1.61 failed with a TypeError on workbooks written by openpyxl, including pandas output, because it does not find the relationships namespace when it is declared on the sheet elements. Files from Excel and XlsxWriter read correctly.",[10,1837,1838,1841],{},[14,1839,1840],{},"Do tablib and pyexcel need openpyxl?","\nYes, for xlsx files. tablib's xlsx support and the pyexcel-xlsx plugin both use openpyxl underneath.",[37,1843,1845],{"id":1844},"related","Related",[90,1847,1848,1854,1861,1868],{},[93,1849,1850,1851,1853],{},"Up one level: ",[31,1852,34],{"href":33}," — picking tools for Excel work.",[93,1855,1856,1860],{},[31,1857,1859],{"href":1858},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fpick-an-excel-engine-for-xlsx-xlsm-xls-xlsb-and-ods\u002F","Pick an Excel Engine for xlsx, xlsm, xls, xlsb and ods"," — engines by file format.",[93,1862,1863,1867],{},[31,1864,1866],{"href":1865},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fopenpyxl-vs-pandas-for-excel-automation\u002F","openpyxl vs pandas for Excel Automation"," — the heavyweight pairing.",[93,1869,1870,1874],{},[31,1871,1873],{"href":1872},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fspeed-up-pandas-excel-reads-with-the-calamine-engine\u002F","Speed Up pandas Excel Reads with the Calamine Engine"," — calamine inside pandas.",[1876,1877,1878],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":47,"searchDepth":387,"depth":387,"links":1880},[1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892],{"id":39,"depth":387,"text":40},{"id":87,"depth":387,"text":88},{"id":363,"depth":387,"text":364},{"id":1033,"depth":387,"text":1034},{"id":1150,"depth":387,"text":1151},{"id":1401,"depth":387,"text":1402},{"id":1720,"depth":387,"text":1721},{"id":1727,"depth":387,"text":1728},{"id":1768,"depth":387,"text":1769},{"id":1809,"depth":387,"text":1810},{"id":1816,"depth":387,"text":1817},{"id":1844,"depth":387,"text":1845},"2026-10-02","When pandas and openpyxl are more than you need: pylightxl, python-calamine, tablib and pyexcel compared on what they read and write, their dependencies and install size, read speed on a 50,000-row file, the types they return for numbers and dates, and the compatibility surprises found in testing — with a recommendation for each use case.","md",[1897,1899,1901,1903],{"q":1822,"a":1898},"python-calamine, which wraps the Rust calamine crate. It read a 50,000-row sheet in about a tenth of a second in our test, roughly eight times faster than openpyxl in read-only mode. pandas can use it with engine=\"calamine\".",{"q":1828,"a":1900},"pylightxl is a single pure-Python module with no dependencies that reads and writes cell values. XlsxWriter also has no dependencies but only writes.",{"q":1834,"a":1902},"pylightxl 1.61 failed with a TypeError on workbooks written by openpyxl, including pandas output, because it does not find the relationships namespace when it is declared on the sheet elements. Files from Excel and XlsxWriter read correctly.",{"q":1840,"a":1904},"Yes, for xlsx files. tablib's xlsx support and the pyexcel-xlsx plugin both use openpyxl underneath.","lightweight python excel libraries compared",null,{"breadcrumb":1908},[1909,1912,1915],{"name":1910,"item":1911},"Home","\u002F",{"name":1913,"item":1914},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":34,"item":33},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Flightweight-python-excel-libraries-compared",{"title":1918,"description":1919},"Lightweight Python Excel Libraries Compared (pylightxl, calamine)","pylightxl, python-calamine, tablib and pyexcel versus openpyxl: read and write support, dependencies, install size, read speed on 50,000 rows, returned types for dates and numbers, and compatibility gotchas.","lightweight-python-excel-libraries-compared","getting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Flightweight-python-excel-libraries-compared\u002Findex",[1923],"choosing-libraries","how-to","snRx2gw6DPwkMYOzLsM0lDUqSmQ-Rhttlyldlgm4YPk",[1927,1931],{"title":1928,"path":1929,"stem":1930,"children":-1},"Google Sheets vs Excel Files for Python Automation","\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fgoogle-sheets-vs-excel-files-for-python-automation","getting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fgoogle-sheets-vs-excel-files-for-python-automation\u002Findex",{"title":1866,"path":1932,"stem":1933,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fopenpyxl-vs-pandas-for-excel-automation","getting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fopenpyxl-vs-pandas-for-excel-automation\u002Findex",[1935,1937],{"path":1936,"title":34},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library",{"path":1938,"title":1913},"\u002Fgetting-started-with-python-excel-automation",[],[1941,1945,1948,1951,1952,1953],{"path":1942,"title":1943,"kind":1944},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fxlwings-vs-pywin32-for-excel-automation","xlwings vs pywin32 for Excel Automation","compare",{"path":1946,"title":1947,"kind":1944},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fopenpyxl-vs-xlsxwriter-vs-pandas-excelwriter","openpyxl vs xlsxwriter vs pandas.ExcelWriter",{"path":1949,"title":1950,"kind":1924},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Fchoose-a-scheduler-for-python-excel-reports","Choose a Scheduler for Python Excel Reports",{"path":1929,"title":1928,"kind":1944},{"path":1932,"title":1866,"kind":1944},{"path":1954,"title":1955,"kind":1944},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fpandas-vs-duckdb-for-excel-data","pandas vs DuckDB for Excel Data",1790972057216]