[{"data":1,"prerenderedAt":2510},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32":2503},{"id":4,"title":5,"body":6,"dateModified":2472,"datePublished":2472,"description":2473,"extension":2474,"faq":2475,"meta":2488,"navigation":165,"path":2496,"seo":2497,"slug":2499,"stem":2500,"type":2501,"__hash__":2502},"docs\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Findex.md","Automating Excel with COM and pywin32",{"type":7,"value":8,"toc":2455},"minimark",[9,19,112,115,120,132,330,343,349,353,435,438,529,532,536,539,615,626,630,633,714,721,810,841,845,851,963,974,978,1049,1052,1063,1067,1073,1168,1179,1402,1409,1413,1416,1512,1665,1680,1684,1690,1813,1827,1839,1843,1846,2014,2024,2034,2038,2041,2048,2051,2055,2058,2306,2319,2322,2326,2355,2359,2366,2372,2378,2384,2390,2396,2400,2451],[10,11,12,13,18],"p",{},"When a job needs something only Excel can do — recalculate a model with Excel's own engine, run a\nmacro somebody wrote in 2014, refresh a Power Query connection, print a range exactly as Excel\nwould — the route is COM automation, and on Windows that means pywin32. This section of\n",[14,15,17],"a",{"href":16},"\u002Fgetting-started-with-python-excel-automation\u002F","Getting Started with Python Excel Automation","\ncovers the raw interface: how to attach to Excel, how to move data across the boundary without\npaying for it a cell at a time, and how to make sure the process you started actually goes away.",[20,21,29,30,29,34,29,38,29,45,29,55,29,62,29,67,29,72,29,77,29,80,29,85,29,90,29,93,29,98,29,103,29,106],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"0 0 740 320","img",[25,26],"com-stack-t","com-stack-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:740px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"What sits between a Python script and the workbook",[35,36,37],"desc",{"id":26},"A Python script calls pywin32, which marshals the call across a process boundary into the Excel application, which is the only layer that actually touches the workbook file.",[39,40],"rect",{"x":41,"y":41,"width":42,"height":43,"fill":44},"0","740","320","#ffffff",[39,46],{"x":47,"y":48,"width":49,"height":50,"rx":51,"fill":52,"stroke":53,"style":54},"26","28","688","56","10","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[56,57,61],"text",{"x":58,"y":59,"style":60},"48","62","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start","your script",[56,63,66],{"x":64,"y":59,"style":65},"692","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:end","plain Python",[39,68],{"x":47,"y":69,"width":49,"height":50,"rx":51,"fill":70,"stroke":71,"style":54},"96","#d9f4f1","var(--teal,#0f9488)",[56,73,76],{"x":58,"y":74,"style":75},"130","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","pywin32",[56,78,79],{"x":64,"y":74,"style":65},"marshals each call",[39,81],{"x":47,"y":82,"width":49,"height":50,"rx":51,"fill":83,"stroke":84,"style":54},"164","#fdefd8","var(--gold,#b4740a)",[56,86,89],{"x":58,"y":87,"style":88},"198","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","EXCEL.EXE",[56,91,92],{"x":64,"y":87,"style":65},"a separate process",[39,94],{"x":47,"y":95,"width":49,"height":50,"rx":51,"fill":96,"stroke":97,"style":54},"232","#e7ebef","var(--line,#cdd5e6)",[56,99,102],{"x":58,"y":100,"style":101},"266","font-size:13.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:start","the workbook",[56,104,105],{"x":64,"y":100,"style":65},"opened by Excel, not by you",[56,107,111],{"x":108,"y":109,"style":110},"370.0","308","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","you are not reading the file — you are asking Excel to",[10,113,114],{},"COM is a different mental model from every other library on this site. openpyxl and xlsxwriter read\nand write a file; pywin32 sends instructions to a running program and reads its answers. That gives\nyou everything Excel can do and none of the guarantees a file-level library provides — no headless\nmode, no cross-platform support, and a hard requirement that somebody eventually closes the\napplication.",[116,117,119],"h2",{"id":118},"attaching-to-excel-and-the-shape-of-a-session","Attaching to Excel and the shape of a session",[10,121,122,123,127,128,131],{},"Every script follows the same skeleton: get an ",[124,125,126],"code",{},"Excel.Application"," object, turn off the things that\nmake Excel interactive, do the work, and quit in a ",[124,129,130],{},"finally"," block so an exception cannot leave a\nprocess behind.",[133,134,139],"pre",{"className":135,"code":136,"language":137,"meta":138,"style":138},"language-python shiki shiki-themes github-light github-dark-high-contrast","import win32com.client as win32\n\nexcel = win32.gencache.EnsureDispatch(\"Excel.Application\")\nexcel.Visible = False\nexcel.DisplayAlerts = False\ntry:\n    book = excel.Workbooks.Open(r\"C:\\reports\\model.xlsx\")\n    try:\n        book.Sheets(\"Summary\").Range(\"B2\").Value = 128400\n        book.Save()\n    finally:\n        book.Close(SaveChanges=False)\nfinally:\n    excel.Quit()\n","python","",[124,140,141,160,167,186,198,208,217,255,263,286,292,300,317,324],{"__ignoreMap":138},[142,143,146,150,154,157],"span",{"class":144,"line":145},"line",1,[142,147,149],{"class":148},"s-kum","import",[142,151,153],{"class":152},"skGVy"," win32com.client ",[142,155,156],{"class":148},"as",[142,158,159],{"class":152}," win32\n",[142,161,163],{"class":144,"line":162},2,[142,164,166],{"emptyLinePlaceholder":165},true,"\n",[142,168,170,173,176,179,183],{"class":144,"line":169},3,[142,171,172],{"class":152},"excel ",[142,174,175],{"class":148},"=",[142,177,178],{"class":152}," win32.gencache.EnsureDispatch(",[142,180,182],{"class":181},"srMev","\"Excel.Application\"",[142,184,185],{"class":152},")\n",[142,187,189,192,194],{"class":144,"line":188},4,[142,190,191],{"class":152},"excel.Visible ",[142,193,175],{"class":148},[142,195,197],{"class":196},"sP0c6"," False\n",[142,199,201,204,206],{"class":144,"line":200},5,[142,202,203],{"class":152},"excel.DisplayAlerts ",[142,205,175],{"class":148},[142,207,197],{"class":196},[142,209,211,214],{"class":144,"line":210},6,[142,212,213],{"class":148},"try",[142,215,216],{"class":152},":\n",[142,218,220,223,225,228,231,234,238,241,244,247,250,253],{"class":144,"line":219},7,[142,221,222],{"class":152},"    book ",[142,224,175],{"class":148},[142,226,227],{"class":152}," excel.Workbooks.Open(",[142,229,230],{"class":148},"r",[142,232,233],{"class":181},"\"C:",[142,235,237],{"class":236},"s_b0D","\\r",[142,239,240],{"class":181},"eports",[142,242,243],{"class":236},"\\m",[142,245,246],{"class":181},"odel",[142,248,249],{"class":196},".",[142,251,252],{"class":181},"xlsx\"",[142,254,185],{"class":152},[142,256,258,261],{"class":144,"line":257},8,[142,259,260],{"class":148},"    try",[142,262,216],{"class":152},[142,264,266,269,272,275,278,281,283],{"class":144,"line":265},9,[142,267,268],{"class":152},"        book.Sheets(",[142,270,271],{"class":181},"\"Summary\"",[142,273,274],{"class":152},").Range(",[142,276,277],{"class":181},"\"B2\"",[142,279,280],{"class":152},").Value ",[142,282,175],{"class":148},[142,284,285],{"class":196}," 128400\n",[142,287,289],{"class":144,"line":288},10,[142,290,291],{"class":152},"        book.Save()\n",[142,293,295,298],{"class":144,"line":294},11,[142,296,297],{"class":148},"    finally",[142,299,216],{"class":152},[142,301,303,306,310,312,315],{"class":144,"line":302},12,[142,304,305],{"class":152},"        book.Close(",[142,307,309],{"class":308},"sa561","SaveChanges",[142,311,175],{"class":148},[142,313,314],{"class":196},"False",[142,316,185],{"class":152},[142,318,320,322],{"class":144,"line":319},13,[142,321,130],{"class":148},[142,323,216],{"class":152},[142,325,327],{"class":144,"line":326},14,[142,328,329],{"class":152},"    excel.Quit()\n",[10,331,332,335,336,339,340,249],{},[124,333,334],{},"EnsureDispatch"," builds a cached type library the first time it runs, which gives you early binding:\nnamed constants, faster calls and a much better error when a method does not exist.\n",[124,337,338],{},"win32.Dispatch"," works without the cache but resolves everything at runtime, so a typo becomes an\nunhelpful COM exception rather than an ",[124,341,342],{},"AttributeError",[10,344,345,348],{},[124,346,347],{},"DisplayAlerts = False"," is not cosmetic. Without it a workbook that wants to ask \"save changes?\"\nopens a modal dialog that nobody can see and nobody can dismiss, and the script hangs indefinitely.",[116,350,352],{"id":351},"moving-data-across-the-boundary","Moving data across the boundary",[20,354,29,360,29,363,29,366,29,370,29,375,29,381,29,390,29,396,29,400,29,403,29,406,29,411,29,415,29,418,29,421,29,426,29,430],{"viewBox":355,"role":23,"ariaLabelledBy":356,"xmlns":27,"style":359},"0 0 720 196",[357,358],"com-calls-t","com-calls-d","width:100%;max-width:720px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,361,362],{"id":357},"Why a COM script is slow: crossings, not bytes",[35,364,365],{"id":358},"Assigning twenty thousand cells one at a time makes twenty thousand process crossings. Assigning the same block in a single range write makes one, and is hundreds of times faster.",[39,367],{"x":41,"y":41,"width":368,"height":369,"fill":44},"720","196",[56,371,374],{"x":372,"y":50,"style":373},"20","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:start","20,000 cell writes",[39,376],{"x":377,"y":378,"width":379,"height":47,"rx":380,"fill":96,"stroke":97},"200","40","371.6","6",[39,382],{"x":383,"y":384,"width":385,"height":386,"rx":387,"fill":388,"stroke":389},"201","41","369.6","24","5","#fee8f2","var(--accent,#d81b73)",[56,391,395],{"x":392,"y":393,"style":394},"583.6","58","font-size:12px;font-weight:700;fill:var(--accent,#d81b73);text-anchor:start","20,000 crossings",[56,397,399],{"x":372,"y":398,"style":373},"100","20 block writes",[39,401],{"x":377,"y":402,"width":379,"height":47,"rx":380,"fill":96,"stroke":97},"84",[39,404],{"x":383,"y":405,"width":48,"height":386,"rx":387,"fill":83,"stroke":84},"85",[56,407,410],{"x":392,"y":408,"style":409},"102","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","20 crossings",[56,412,414],{"x":372,"y":413,"style":373},"144","1 range assignment",[39,416],{"x":377,"y":417,"width":379,"height":47,"rx":380,"fill":96,"stroke":97},"128",[39,419],{"x":383,"y":420,"width":48,"height":386,"rx":387,"fill":70,"stroke":71},"129",[56,422,425],{"x":392,"y":423,"style":424},"146","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","1 crossing",[56,427,429],{"x":372,"y":372,"style":428},"font-size:11.5px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:start","relative cost",[56,431,434],{"x":432,"y":433,"style":110},"360.0","186","batch the boundary, not the data",[10,436,437],{},"The single most important performance fact about COM is that the cost is per call, not per cell.\nReading one cell and reading a 20,000-cell block cost roughly the same, because the expensive part\nis crossing the process boundary. A loop that assigns cells one at a time will take minutes where a\nsingle range assignment takes under a second.",[133,439,441],{"className":135,"code":440,"language":137,"meta":138,"style":138},"sheet = book.Sheets(\"Data\")\n\n# Read a whole block in one call — comes back as a tuple of tuples.\nrows = sheet.Range(\"A2:D5001\").Value\n\n# Write a whole block in one call — needs a sequence of row sequences.\nsheet.Range(\"F2:F5001\").Value = [[value * 1.2] for value in column]\n",[124,442,443,458,462,468,484,488,493],{"__ignoreMap":138},[142,444,445,448,450,453,456],{"class":144,"line":145},[142,446,447],{"class":152},"sheet ",[142,449,175],{"class":148},[142,451,452],{"class":152}," book.Sheets(",[142,454,455],{"class":181},"\"Data\"",[142,457,185],{"class":152},[142,459,460],{"class":144,"line":162},[142,461,166],{"emptyLinePlaceholder":165},[142,463,464],{"class":144,"line":169},[142,465,467],{"class":466},"s-wDw","# Read a whole block in one call — comes back as a tuple of tuples.\n",[142,469,470,473,475,478,481],{"class":144,"line":188},[142,471,472],{"class":152},"rows ",[142,474,175],{"class":148},[142,476,477],{"class":152}," sheet.Range(",[142,479,480],{"class":181},"\"A2:D5001\"",[142,482,483],{"class":152},").Value\n",[142,485,486],{"class":144,"line":200},[142,487,166],{"emptyLinePlaceholder":165},[142,489,490],{"class":144,"line":210},[142,491,492],{"class":466},"# Write a whole block in one call — needs a sequence of row sequences.\n",[142,494,495,498,501,503,505,508,511,514,517,520,523,526],{"class":144,"line":219},[142,496,497],{"class":152},"sheet.Range(",[142,499,500],{"class":181},"\"F2:F5001\"",[142,502,280],{"class":152},[142,504,175],{"class":148},[142,506,507],{"class":152}," [[value ",[142,509,510],{"class":148},"*",[142,512,513],{"class":196}," 1.2",[142,515,516],{"class":152},"] ",[142,518,519],{"class":148},"for",[142,521,522],{"class":152}," value ",[142,524,525],{"class":148},"in",[142,527,528],{"class":152}," column]\n",[10,530,531],{},"The shape is strict on the way in: Excel expects a two-dimensional structure, so a flat list assigned\nto a column range raises or fills only the first cell. Wrapping each value in its own list, as above,\nis the fix people spend an hour discovering.",[116,533,535],{"id":534},"running-the-things-that-only-exist-inside-excel","Running the things that only exist inside Excel",[10,537,538],{},"Macros, add-ins, pivot refreshes and query connections are all reachable, and all use the same\npattern: find the object in Excel's model and call the method the VBA editor would have called.",[133,540,542],{"className":135,"code":541,"language":137,"meta":138,"style":138},"book = excel.Workbooks.Open(r\"C:\\reports\\dashboard.xlsm\")\nexcel.Application.Run(\"dashboard.xlsm!RebuildSummary\", \"2026-Q3\")\n\nbook.RefreshAll()                      # queries and pivot caches\nexcel.CalculateUntilAsyncQueriesDone() # wait for them to finish\nbook.Save()\n",[124,543,544,574,590,594,602,610],{"__ignoreMap":138},[142,545,546,549,551,553,555,557,559,561,564,567,569,572],{"class":144,"line":145},[142,547,548],{"class":152},"book ",[142,550,175],{"class":148},[142,552,227],{"class":152},[142,554,230],{"class":148},[142,556,233],{"class":181},[142,558,237],{"class":236},[142,560,240],{"class":181},[142,562,563],{"class":196},"\\d",[142,565,566],{"class":181},"ashboard",[142,568,249],{"class":196},[142,570,571],{"class":181},"xlsm\"",[142,573,185],{"class":152},[142,575,576,579,582,585,588],{"class":144,"line":162},[142,577,578],{"class":152},"excel.Application.Run(",[142,580,581],{"class":181},"\"dashboard.xlsm!RebuildSummary\"",[142,583,584],{"class":152},", ",[142,586,587],{"class":181},"\"2026-Q3\"",[142,589,185],{"class":152},[142,591,592],{"class":144,"line":169},[142,593,166],{"emptyLinePlaceholder":165},[142,595,596,599],{"class":144,"line":188},[142,597,598],{"class":152},"book.RefreshAll()                      ",[142,600,601],{"class":466},"# queries and pivot caches\n",[142,603,604,607],{"class":144,"line":200},[142,605,606],{"class":152},"excel.CalculateUntilAsyncQueriesDone() ",[142,608,609],{"class":466},"# wait for them to finish\n",[142,611,612],{"class":144,"line":210},[142,613,614],{"class":152},"book.Save()\n",[10,616,617,620,621,625],{},[124,618,619],{},"RefreshAll"," returns immediately — it starts the refresh rather than completing it — which is why\nthe wait call matters. Saving without it produces a workbook containing the previous run's numbers,\na failure that is invisible until somebody notices the report has not moved.\n",[14,622,624],{"href":623},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frefresh-excel-data-connections-and-recalculate-with-win32com\u002F","Refresh Excel Data Connections and Recalculate with win32com","\ncovers the timing in detail.",[116,627,629],{"id":628},"translating-vba-into-python","Translating VBA into Python",[10,631,632],{},"The most useful property of COM automation is that Excel documents it for you. Record a macro,\nopen the VBA editor, and the generated code names the exact objects and methods to call — the\ntranslation to Python is nearly mechanical.",[20,634,29,640,29,643,29,646,29,649,29,655,29,661,29,667,29,672,29,677,29,682,29,685,29,689,29,692,29,696,29,699,29,702,29,707,29,710],{"viewBox":635,"role":23,"ariaLabelledBy":636,"xmlns":27,"style":639},"0 0 760 232",[637,638],"com-vba-t","com-vba-d","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,641,642],{"id":637},"Recorded VBA is the documentation for the Python call",[35,644,645],{"id":638},"Recording a macro in Excel produces VBA that names the exact objects and methods, which translate to Python almost line for line through three mechanical rules.",[39,647],{"x":41,"y":41,"width":648,"height":95,"fill":44},"760",[56,650,654],{"x":651,"y":652,"style":653},"380.0","32","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","translating VBA",[39,656],{"x":657,"y":658,"width":659,"height":69,"rx":660,"fill":83,"stroke":84,"style":54},"24.0","74","208.0","12",[56,662,666],{"x":663,"y":664,"style":665},"128.0","114","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","record a macro",[56,668,671],{"x":663,"y":669,"style":670},"136","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Excel writes the VBA",[144,673],{"x1":674,"y1":675,"x2":676,"y2":675,"stroke":53,"style":54},"237.0","122.0","269.0",[678,679],"polygon",{"points":680,"fill":681},"269.0,122.0 260.0,117.0 260.0,127.0","#5b5cf0",[39,683],{"x":684,"y":658,"width":659,"height":69,"rx":660,"fill":52,"stroke":53,"style":54},"276.0",[56,686,688],{"x":651,"y":664,"style":687},"font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","read the object model",[56,690,691],{"x":651,"y":669,"style":670},"names are identical",[144,693],{"x1":694,"y1":675,"x2":695,"y2":675,"stroke":53,"style":54},"489.0","521.0",[678,697],{"points":698,"fill":681},"521.0,122.0 512.0,117.0 512.0,127.0",[39,700],{"x":701,"y":658,"width":659,"height":69,"rx":660,"fill":70,"stroke":71,"style":54},"528.0",[56,703,706],{"x":704,"y":664,"style":705},"632.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","call it from Python",[56,708,709],{"x":704,"y":669,"style":670},"add parentheses, drop Set",[56,711,713],{"x":651,"y":712,"style":110},"210","the object model is the same; only the syntax changes",[133,715,719],{"className":716,"code":718,"language":56,"meta":138},[717],"language-text","' VBA, recorded in Excel\nSheets(\"Data\").Range(\"A1:D100\").Sort Key1:=Range(\"B1\"), Order1:=xlAscending, Header:=xlYes\n",[124,720,718],{"__ignoreMap":138},[133,722,724],{"className":135,"code":723,"language":137,"meta":138,"style":138},"# The same call from Python\nXL_ASCENDING, XL_YES = 1, 1\nsheet = book.Sheets(\"Data\")\nsheet.Range(\"A1:D100\").Sort(\n    Key1=sheet.Range(\"B1\"), Order1=XL_ASCENDING, Header=XL_YES\n)\n",[124,725,726,731,752,764,774,806],{"__ignoreMap":138},[142,727,728],{"class":144,"line":145},[142,729,730],{"class":466},"# The same call from Python\n",[142,732,733,736,738,741,744,747,749],{"class":144,"line":162},[142,734,735],{"class":196},"XL_ASCENDING",[142,737,584],{"class":152},[142,739,740],{"class":196},"XL_YES",[142,742,743],{"class":148}," =",[142,745,746],{"class":196}," 1",[142,748,584],{"class":152},[142,750,751],{"class":196},"1\n",[142,753,754,756,758,760,762],{"class":144,"line":169},[142,755,447],{"class":152},[142,757,175],{"class":148},[142,759,452],{"class":152},[142,761,455],{"class":181},[142,763,185],{"class":152},[142,765,766,768,771],{"class":144,"line":188},[142,767,497],{"class":152},[142,769,770],{"class":181},"\"A1:D100\"",[142,772,773],{"class":152},").Sort(\n",[142,775,776,779,781,783,786,789,792,794,796,798,801,803],{"class":144,"line":200},[142,777,778],{"class":308},"    Key1",[142,780,175],{"class":148},[142,782,497],{"class":152},[142,784,785],{"class":181},"\"B1\"",[142,787,788],{"class":152},"), ",[142,790,791],{"class":308},"Order1",[142,793,175],{"class":148},[142,795,735],{"class":196},[142,797,584],{"class":152},[142,799,800],{"class":308},"Header",[142,802,175],{"class":148},[142,804,805],{"class":196},"XL_YES\n",[142,807,808],{"class":144,"line":210},[142,809,185],{"class":152},[10,811,812,813,816,817,584,820,816,823,826,827,584,830,833,834,837,838,840],{},"Three rules cover most of it: ",[124,814,815],{},".Method arg"," becomes ",[124,818,819],{},".Method(arg)",[124,821,822],{},"Set x = y",[124,824,825],{},"x = y",", and\nVBA's named constants (",[124,828,829],{},"xlAscending",[124,831,832],{},"xlYes",") become integers — available as\n",[124,835,836],{},"win32com.client.constants.xlAscending"," once ",[124,839,334],{}," has built the type cache.",[116,842,844],{"id":843},"the-process-lifetime-problem","The process-lifetime problem",[10,846,847,848,850],{},"Every other library on this site releases its resources when the object goes out of scope. COM does\nnot: Excel stays alive while any reference to it exists, including references held by a traceback in\nan interactive session. An orphaned ",[124,849,89],{}," holds a lock on the workbook it had open, and after\na few scheduled runs the machine has several of them.",[133,852,854],{"className":135,"code":853,"language":137,"meta":138,"style":138},"import pythoncom\nimport win32com.client as win32\n\ndef with_excel(work):\n    pythoncom.CoInitialize()\n    excel = win32.gencache.EnsureDispatch(\"Excel.Application\")\n    excel.Visible = False\n    excel.DisplayAlerts = False\n    try:\n        return work(excel)\n    finally:\n        excel.Quit()\n        del excel\n        pythoncom.CoUninitialize()\n",[124,855,856,863,873,877,889,894,907,916,925,931,939,945,950,958],{"__ignoreMap":138},[142,857,858,860],{"class":144,"line":145},[142,859,149],{"class":148},[142,861,862],{"class":152}," pythoncom\n",[142,864,865,867,869,871],{"class":144,"line":162},[142,866,149],{"class":148},[142,868,153],{"class":152},[142,870,156],{"class":148},[142,872,159],{"class":152},[142,874,875],{"class":144,"line":169},[142,876,166],{"emptyLinePlaceholder":165},[142,878,879,882,886],{"class":144,"line":188},[142,880,881],{"class":148},"def",[142,883,885],{"class":884},"s_Opv"," with_excel",[142,887,888],{"class":152},"(work):\n",[142,890,891],{"class":144,"line":200},[142,892,893],{"class":152},"    pythoncom.CoInitialize()\n",[142,895,896,899,901,903,905],{"class":144,"line":210},[142,897,898],{"class":152},"    excel ",[142,900,175],{"class":148},[142,902,178],{"class":152},[142,904,182],{"class":181},[142,906,185],{"class":152},[142,908,909,912,914],{"class":144,"line":219},[142,910,911],{"class":152},"    excel.Visible ",[142,913,175],{"class":148},[142,915,197],{"class":196},[142,917,918,921,923],{"class":144,"line":257},[142,919,920],{"class":152},"    excel.DisplayAlerts ",[142,922,175],{"class":148},[142,924,197],{"class":196},[142,926,927,929],{"class":144,"line":265},[142,928,260],{"class":148},[142,930,216],{"class":152},[142,932,933,936],{"class":144,"line":288},[142,934,935],{"class":148},"        return",[142,937,938],{"class":152}," work(excel)\n",[142,940,941,943],{"class":144,"line":294},[142,942,297],{"class":148},[142,944,216],{"class":152},[142,946,947],{"class":144,"line":302},[142,948,949],{"class":152},"        excel.Quit()\n",[142,951,952,955],{"class":144,"line":319},[142,953,954],{"class":148},"        del",[142,956,957],{"class":152}," excel\n",[142,959,960],{"class":144,"line":326},[142,961,962],{"class":152},"        pythoncom.CoUninitialize()\n",[10,964,965,968,969,973],{},[124,966,967],{},"CoInitialize"," is required in any thread that is not the main one — a Flask request handler, a\nworker thread, an APScheduler job — and its absence produces a confusing \"CoInitialize has not been\ncalled\" error rather than anything about threads.\n",[14,970,972],{"href":971},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fclose-excel-cleanly-and-avoid-orphan-com-processes\u002F","Close Excel Cleanly and Avoid Orphan COM Processes","\ncovers the full lifecycle, including detecting the orphans you already have.",[116,975,977],{"id":976},"when-to-use-this-instead-of-xlwings","When to use this instead of xlwings",[20,979,29,983,29,986,29,989,29,991,29,996,29,1002,29,1007,29,1011,29,1015,29,1018,29,1022,29,1025,29,1029,29,1033,29,1036,29,1039,29,1042,29,1045],{"viewBox":635,"role":23,"ariaLabelledBy":980,"xmlns":27,"style":639},[981,982],"com-choice-t","com-choice-d",[31,984,985],{"id":981},"pywin32 or xlwings for the same capability",[35,987,988],{"id":982},"xlwings wraps the COM interface with DataFrame conversion and a context manager, while raw pywin32 matches Excel's documented object model exactly and needs one fewer dependency.",[39,990],{"x":41,"y":41,"width":648,"height":95,"fill":44},[39,992],{"x":993,"y":47,"width":994,"height":82,"rx":995,"fill":70,"stroke":71,"style":54},"20.0","350.0","14",[56,997,1001],{"x":998,"y":999,"style":1000},"195.0","52","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","xlwings",[144,1003],{"x1":1004,"y1":59,"x2":1005,"y2":59,"stroke":71,"style":1006},"36.0","354.0","stroke-width:1px",[56,1008,1010],{"x":998,"y":402,"style":1009},"font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","DataFrames in and out",[56,1012,1014],{"x":998,"y":1013,"style":1009},"107","context manager quits",[56,1016,1017],{"x":998,"y":74,"style":1009},"Pythonic objects",[56,1019,1021],{"x":998,"y":1020,"style":1009},"153","escape hatch via .api",[39,1023],{"x":1024,"y":47,"width":994,"height":82,"rx":995,"fill":52,"stroke":53,"style":54},"390.0",[56,1026,76],{"x":1027,"y":999,"style":1028},"565.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[144,1030],{"x1":1031,"y1":59,"x2":1032,"y2":59,"stroke":53,"style":1006},"406.0","724.0",[56,1034,1035],{"x":1027,"y":402,"style":1009},"Excel's model exactly",[56,1037,1038],{"x":1027,"y":1013,"style":1009},"matches recorded VBA",[56,1040,1041],{"x":1027,"y":74,"style":1009},"one dependency",[56,1043,1044],{"x":1027,"y":1020,"style":1009},"you manage the lifecycle",[56,1046,1048],{"x":651,"y":1047,"style":110},"218","same capability, different ergonomics",[10,1050,1051],{},"xlwings wraps this same interface and hides most of the sharp edges: it converts ranges to and from\nDataFrames, quits the application through a context manager, and gives objects that behave like\nPython. For most work it is simply the better ergonomics on the same capability.",[10,1053,1054,1055,1058,1059,1062],{},"Raw pywin32 earns its place in three situations. When you are translating an existing VBA macro\nalmost line for line, matching the object model exactly is easier than mapping it onto a wrapper.\nWhen you need a corner of Excel that xlwings has not wrapped — mail merges, some chart properties,\n",[124,1056,1057],{},"Application","-level settings — you end up on ",[124,1060,1061],{},".api"," anyway, which is a pywin32 object. And when the\ndependency footprint matters, pywin32 is one package rather than two.",[116,1064,1066],{"id":1065},"attaching-to-a-workbook-that-is-already-open","Attaching to a workbook that is already open",[10,1068,1069,1070,1072],{},"Three different calls produce an ",[124,1071,1057],{}," object, and picking the wrong one is how a script\ncloses the spreadsheet somebody was working in.",[20,1074,29,1079,29,1082,29,1085,29,1088,29,1093,29,1098,29,1103,29,1109,29,1113,29,1117,29,1120,29,1126,29,1129,29,1133,29,1136,29,1138,29,1141,29,1145,29,1148,29,1152,29,1155,29,1157,29,1160,29,1164],{"viewBox":1075,"role":23,"ariaLabelledBy":1076,"xmlns":27,"style":639},"0 0 760 264",[1077,1078],"com-attach-t","com-attach-d",[31,1080,1081],{"id":1077},"Three ways to get an Application object, and what each does",[35,1083,1084],{"id":1078},"GetActiveObject attaches to a copy of Excel the user already has open, Dispatch reuses one if present and starts one otherwise, and DispatchEx always starts a fresh private instance.",[39,1086],{"x":41,"y":41,"width":648,"height":1087,"fill":44},"264",[39,1089],{"x":1090,"y":47,"width":1091,"height":50,"rx":660,"fill":1092,"stroke":53,"style":54},"230.0","300","#ebebfd",[56,1094,1097],{"x":651,"y":1095,"style":1096},"60","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Is Excel already running?",[39,1099],{"x":1100,"y":1101,"width":1102,"height":402,"rx":660,"fill":83,"stroke":84,"style":54},"18.0","148","226.7",[56,1104,1108],{"x":1105,"y":1106,"style":1107},"131.35","182","font-size:13px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","GetActiveObject",[56,1110,1112],{"x":1105,"y":1111,"style":670},"204","edits what the user sees",[144,1114],{"x1":1105,"y1":1115,"x2":1105,"y2":1116,"stroke":53,"style":54},"87","141",[678,1118],{"points":1119,"fill":681},"131.35,141 126.35,132 136.35,132",[56,1121,1125],{"x":1122,"y":1123,"style":1124},"137.35","116","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:start","attach",[39,1127],{"x":1128,"y":1101,"width":1102,"height":402,"rx":660,"fill":52,"stroke":53,"style":54},"266.7",[56,1130,1132],{"x":1131,"y":1106,"style":1028},"380.04999999999995","Dispatch",[56,1134,1135],{"x":1131,"y":1111,"style":670},"the usual default",[144,1137],{"x1":1131,"y1":1115,"x2":1131,"y2":1116,"stroke":53,"style":54},[678,1139],{"points":1140,"fill":681},"380.04999999999995,141 375.04999999999995,132 385.04999999999995,132",[56,1142,1144],{"x":1143,"y":1123,"style":1124},"386.04999999999995","reuse or start",[39,1146],{"x":1147,"y":1101,"width":1102,"height":402,"rx":660,"fill":70,"stroke":71,"style":54},"515.4",[56,1149,1151],{"x":1150,"y":1106,"style":1000},"628.75","DispatchEx",[56,1153,1154],{"x":1150,"y":1111,"style":670},"isolated instance",[144,1156],{"x1":1150,"y1":1115,"x2":1150,"y2":1116,"stroke":53,"style":54},[678,1158],{"points":1159,"fill":681},"628.75,141 623.75,132 633.75,132",[56,1161,1163],{"x":1162,"y":1123,"style":1124},"634.75","always fresh",[56,1165,1167],{"x":651,"y":1166,"style":110},"252","choose deliberately — the wrong one closes somebody's work",[10,1169,1170,1172,1173,1175,1176,1178],{},[124,1171,1108],{}," attaches to a copy of Excel that is already running and raises if there is none —\nwhich is what you want when the point is to act on the user's open workbook, including unsaved\nchanges that are not on disk yet. ",[124,1174,1132],{}," reuses a running instance if one exists and starts one\notherwise, which is convenient and occasionally surprising. ",[124,1177,1151],{}," always starts a private\ninstance, which is the safe choice for anything automated because quitting it cannot disturb a\nperson's session.",[133,1180,1182],{"className":135,"code":1181,"language":137,"meta":138,"style":138},"import win32com.client as win32\n\ntry:\n    excel = win32.GetActiveObject(\"Excel.Application\")   # act on what the user has open\n    started_it = False\nexcept Exception:\n    excel = win32.DispatchEx(\"Excel.Application\")        # private instance, safe to quit\n    started_it = True\n\ntry:\n    book = next((b for b in excel.Workbooks if b.Name == \"budget.xlsx\"), None)\n    if book is None:\n        book = excel.Workbooks.Open(r\"C:\\finance\\budget.xlsx\")\n    print(book.Sheets(\"Summary\").Range(\"C12\").Value)\nfinally:\n    if started_it:\n        excel.Quit()\n",[124,1183,1184,1194,1198,1204,1221,1230,1240,1257,1266,1270,1276,1317,1333,1364,1382,1389,1397],{"__ignoreMap":138},[142,1185,1186,1188,1190,1192],{"class":144,"line":145},[142,1187,149],{"class":148},[142,1189,153],{"class":152},[142,1191,156],{"class":148},[142,1193,159],{"class":152},[142,1195,1196],{"class":144,"line":162},[142,1197,166],{"emptyLinePlaceholder":165},[142,1199,1200,1202],{"class":144,"line":169},[142,1201,213],{"class":148},[142,1203,216],{"class":152},[142,1205,1206,1208,1210,1213,1215,1218],{"class":144,"line":188},[142,1207,898],{"class":152},[142,1209,175],{"class":148},[142,1211,1212],{"class":152}," win32.GetActiveObject(",[142,1214,182],{"class":181},[142,1216,1217],{"class":152},")   ",[142,1219,1220],{"class":466},"# act on what the user has open\n",[142,1222,1223,1226,1228],{"class":144,"line":200},[142,1224,1225],{"class":152},"    started_it ",[142,1227,175],{"class":148},[142,1229,197],{"class":196},[142,1231,1232,1235,1238],{"class":144,"line":210},[142,1233,1234],{"class":148},"except",[142,1236,1237],{"class":196}," Exception",[142,1239,216],{"class":152},[142,1241,1242,1244,1246,1249,1251,1254],{"class":144,"line":219},[142,1243,898],{"class":152},[142,1245,175],{"class":148},[142,1247,1248],{"class":152}," win32.DispatchEx(",[142,1250,182],{"class":181},[142,1252,1253],{"class":152},")        ",[142,1255,1256],{"class":466},"# private instance, safe to quit\n",[142,1258,1259,1261,1263],{"class":144,"line":257},[142,1260,1225],{"class":152},[142,1262,175],{"class":148},[142,1264,1265],{"class":196}," True\n",[142,1267,1268],{"class":144,"line":265},[142,1269,166],{"emptyLinePlaceholder":165},[142,1271,1272,1274],{"class":144,"line":288},[142,1273,213],{"class":148},[142,1275,216],{"class":152},[142,1277,1278,1280,1282,1285,1288,1290,1293,1295,1298,1301,1304,1307,1310,1312,1315],{"class":144,"line":294},[142,1279,222],{"class":152},[142,1281,175],{"class":148},[142,1283,1284],{"class":196}," next",[142,1286,1287],{"class":152},"((b ",[142,1289,519],{"class":148},[142,1291,1292],{"class":152}," b ",[142,1294,525],{"class":148},[142,1296,1297],{"class":152}," excel.Workbooks ",[142,1299,1300],{"class":148},"if",[142,1302,1303],{"class":152}," b.Name ",[142,1305,1306],{"class":148},"==",[142,1308,1309],{"class":181}," \"budget.xlsx\"",[142,1311,788],{"class":152},[142,1313,1314],{"class":196},"None",[142,1316,185],{"class":152},[142,1318,1319,1322,1325,1328,1331],{"class":144,"line":302},[142,1320,1321],{"class":148},"    if",[142,1323,1324],{"class":152}," book ",[142,1326,1327],{"class":148},"is",[142,1329,1330],{"class":196}," None",[142,1332,216],{"class":152},[142,1334,1335,1338,1340,1342,1344,1346,1349,1352,1355,1358,1360,1362],{"class":144,"line":319},[142,1336,1337],{"class":152},"        book ",[142,1339,175],{"class":148},[142,1341,227],{"class":152},[142,1343,230],{"class":148},[142,1345,233],{"class":181},[142,1347,1348],{"class":236},"\\f",[142,1350,1351],{"class":181},"inance",[142,1353,1354],{"class":196},"\\b",[142,1356,1357],{"class":181},"udget",[142,1359,249],{"class":196},[142,1361,252],{"class":181},[142,1363,185],{"class":152},[142,1365,1366,1369,1372,1374,1376,1379],{"class":144,"line":326},[142,1367,1368],{"class":196},"    print",[142,1370,1371],{"class":152},"(book.Sheets(",[142,1373,271],{"class":181},[142,1375,274],{"class":152},[142,1377,1378],{"class":181},"\"C12\"",[142,1380,1381],{"class":152},").Value)\n",[142,1383,1385,1387],{"class":144,"line":1384},15,[142,1386,130],{"class":148},[142,1388,216],{"class":152},[142,1390,1392,1394],{"class":144,"line":1391},16,[142,1393,1321],{"class":148},[142,1395,1396],{"class":152}," started_it:\n",[142,1398,1400],{"class":144,"line":1399},17,[142,1401,949],{"class":152},[10,1403,1404,1405,1408],{},"The ",[124,1406,1407],{},"started_it"," flag is the part worth copying. Quitting an application you did not start closes\nevery workbook the user had open, without asking — a one-line bug with a memorable cost.",[116,1410,1412],{"id":1411},"formatting-and-printing-through-excel","Formatting and printing through Excel",[10,1414,1415],{},"Everything Excel can do to a sheet's appearance is reachable, and two of those things have no good\nequivalent in the file-level libraries: printing with Excel's own pagination, and exporting a PDF\nthat matches it exactly.",[20,1417,29,1422,29,1425,29,1428,29,1430,29,1434,29,1439,29,1444,29,1450,29,1455,29,1458,29,1461,29,1465,29,1469,29,1472,29,1475,29,1478,29,1482,29,1486,29,1490,29,1493,29,1496,29,1500,29,1504,29,1508],{"viewBox":1418,"role":23,"ariaLabelledBy":1419,"xmlns":27,"style":639},"0 0 760 308",[1420,1421],"com-print-t","com-print-d",[31,1423,1424],{"id":1420},"Producing Excel-native output from Python",[35,1426,1427],{"id":1421},"Set the page layout properties, choose the print area, then export to PDF through the same code path Excel's own print dialog uses.",[39,1429],{"x":41,"y":41,"width":648,"height":109,"fill":44},[39,1431],{"x":1432,"y":48,"width":1433,"height":999,"rx":51,"fill":70,"stroke":71},"22","716",[1435,1436],"circle",{"cx":999,"cy":1437,"r":1438,"fill":681},"54.0","15",[56,1440,1443],{"x":999,"y":1441,"style":1442},"59.0","font-size:13px;font-weight:700;fill:#ffffff;text-anchor:middle","1",[56,1445,1449],{"x":1446,"y":1447,"style":1448},"82","50","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","Set the page layout",[56,1451,1454],{"x":1446,"y":1452,"style":1453},"68","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:start","orientation, fit-to-width, margins",[39,1456],{"x":1432,"y":1457,"width":1433,"height":999,"rx":51,"fill":70,"stroke":71},"90",[1435,1459],{"cx":999,"cy":1460,"r":1438,"fill":681},"116.0",[56,1462,1464],{"x":999,"y":1463,"style":1442},"121.0","2",[56,1466,1468],{"x":1446,"y":1467,"style":1448},"112","Define the print area",[56,1470,1471],{"x":1446,"y":74,"style":1453},"a range, or the used range of the sheet",[39,1473],{"x":1432,"y":1474,"width":1433,"height":999,"rx":51,"fill":70,"stroke":71},"152",[1435,1476],{"cx":999,"cy":1477,"r":1438,"fill":681},"178.0",[56,1479,1481],{"x":999,"y":1480,"style":1442},"183.0","3",[56,1483,1485],{"x":1446,"y":1484,"style":1448},"174","Add headers and footers",[56,1487,1489],{"x":1446,"y":1488,"style":1453},"192","page numbers, a run date, a file path",[39,1491],{"x":1432,"y":1492,"width":1433,"height":999,"rx":51,"fill":70,"stroke":71},"214",[1435,1494],{"cx":999,"cy":1495,"r":1438,"fill":681},"240.0",[56,1497,1499],{"x":999,"y":1498,"style":1442},"245.0","4",[56,1501,1503],{"x":1446,"y":1502,"style":1448},"236","Export as a fixed format",[56,1505,1507],{"x":1446,"y":1506,"style":1453},"254","ExportAsFixedFormat writes the PDF",[56,1509,1511],{"x":651,"y":1510,"style":110},"296","the output matches what Excel would print, because Excel printed it",[133,1513,1515],{"className":135,"code":1514,"language":137,"meta":138,"style":138},"XL_LANDSCAPE, XL_TYPE_PDF = 2, 0\n\nsheet = book.Sheets(\"Summary\")\nsetup = sheet.PageSetup\nsetup.Orientation = XL_LANDSCAPE\nsetup.Zoom = False\nsetup.FitToPagesWide = 1\nsetup.FitToPagesTall = False\nsetup.PrintArea = \"A1:H60\"\nsetup.CenterFooter = \"Page &P of &N\"\nsetup.LeftHeader = \"&D  Regional revenue\"\n\nbook.ExportAsFixedFormat(XL_TYPE_PDF, r\"C:\\reports\\summary.pdf\")\n",[124,1516,1517,1537,1541,1553,1563,1573,1582,1592,1601,1611,1621,1631,1635],{"__ignoreMap":138},[142,1518,1519,1522,1524,1527,1529,1532,1534],{"class":144,"line":145},[142,1520,1521],{"class":196},"XL_LANDSCAPE",[142,1523,584],{"class":152},[142,1525,1526],{"class":196},"XL_TYPE_PDF",[142,1528,743],{"class":148},[142,1530,1531],{"class":196}," 2",[142,1533,584],{"class":152},[142,1535,1536],{"class":196},"0\n",[142,1538,1539],{"class":144,"line":162},[142,1540,166],{"emptyLinePlaceholder":165},[142,1542,1543,1545,1547,1549,1551],{"class":144,"line":169},[142,1544,447],{"class":152},[142,1546,175],{"class":148},[142,1548,452],{"class":152},[142,1550,271],{"class":181},[142,1552,185],{"class":152},[142,1554,1555,1558,1560],{"class":144,"line":188},[142,1556,1557],{"class":152},"setup ",[142,1559,175],{"class":148},[142,1561,1562],{"class":152}," sheet.PageSetup\n",[142,1564,1565,1568,1570],{"class":144,"line":200},[142,1566,1567],{"class":152},"setup.Orientation ",[142,1569,175],{"class":148},[142,1571,1572],{"class":196}," XL_LANDSCAPE\n",[142,1574,1575,1578,1580],{"class":144,"line":210},[142,1576,1577],{"class":152},"setup.Zoom ",[142,1579,175],{"class":148},[142,1581,197],{"class":196},[142,1583,1584,1587,1589],{"class":144,"line":219},[142,1585,1586],{"class":152},"setup.FitToPagesWide ",[142,1588,175],{"class":148},[142,1590,1591],{"class":196}," 1\n",[142,1593,1594,1597,1599],{"class":144,"line":257},[142,1595,1596],{"class":152},"setup.FitToPagesTall ",[142,1598,175],{"class":148},[142,1600,197],{"class":196},[142,1602,1603,1606,1608],{"class":144,"line":265},[142,1604,1605],{"class":152},"setup.PrintArea ",[142,1607,175],{"class":148},[142,1609,1610],{"class":181}," \"A1:H60\"\n",[142,1612,1613,1616,1618],{"class":144,"line":288},[142,1614,1615],{"class":152},"setup.CenterFooter ",[142,1617,175],{"class":148},[142,1619,1620],{"class":181}," \"Page &P of &N\"\n",[142,1622,1623,1626,1628],{"class":144,"line":294},[142,1624,1625],{"class":152},"setup.LeftHeader ",[142,1627,175],{"class":148},[142,1629,1630],{"class":181}," \"&D  Regional revenue\"\n",[142,1632,1633],{"class":144,"line":302},[142,1634,166],{"emptyLinePlaceholder":165},[142,1636,1637,1640,1642,1644,1646,1648,1650,1652,1655,1658,1660,1663],{"class":144,"line":319},[142,1638,1639],{"class":152},"book.ExportAsFixedFormat(",[142,1641,1526],{"class":196},[142,1643,584],{"class":152},[142,1645,230],{"class":148},[142,1647,233],{"class":181},[142,1649,237],{"class":236},[142,1651,240],{"class":181},[142,1653,1654],{"class":196},"\\s",[142,1656,1657],{"class":181},"ummary",[142,1659,249],{"class":196},[142,1661,1662],{"class":181},"pdf\"",[142,1664,185],{"class":152},[10,1666,1667,1670,1671,1674,1675,1679],{},[124,1668,1669],{},"Zoom = False"," is required before ",[124,1672,1673],{},"FitToPagesWide"," has any effect — Excel treats zoom and fit-to-page\nas mutually exclusive, and setting the second while the first is active silently does nothing. The\nsame page-setup properties are available from openpyxl for the layout itself, as\n",[14,1676,1678],{"href":1677},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-print-area-and-page-setup-with-openpyxl\u002F","Set the Print Area and Page Setup with openpyxl","\nshows; what openpyxl cannot do is produce the PDF.",[116,1681,1683],{"id":1682},"constants-errors-and-the-type-cache","Constants, errors and the type cache",[10,1685,1686,1687,1689],{},"Early binding through ",[124,1688,334],{}," does more than speed calls up: it generates a Python module\ndescribing Excel's type library, which is what makes named constants and useful error messages\navailable.",[133,1691,1693],{"className":135,"code":1692,"language":137,"meta":138,"style":138},"import win32com.client as win32\nfrom win32com.client import constants\n\nexcel = win32.gencache.EnsureDispatch(\"Excel.Application\")\nsheet = excel.Workbooks.Open(r\"C:\\data\\orders.xlsx\").Sheets(1)\nsheet.Range(\"A1:D500\").Sort(\n    Key1=sheet.Range(\"B1\"),\n    Order1=constants.xlAscending,\n    Header=constants.xlYes,\n)\n",[124,1694,1695,1705,1717,1721,1733,1767,1776,1789,1799,1809],{"__ignoreMap":138},[142,1696,1697,1699,1701,1703],{"class":144,"line":145},[142,1698,149],{"class":148},[142,1700,153],{"class":152},[142,1702,156],{"class":148},[142,1704,159],{"class":152},[142,1706,1707,1710,1712,1714],{"class":144,"line":162},[142,1708,1709],{"class":148},"from",[142,1711,153],{"class":152},[142,1713,149],{"class":148},[142,1715,1716],{"class":152}," constants\n",[142,1718,1719],{"class":144,"line":169},[142,1720,166],{"emptyLinePlaceholder":165},[142,1722,1723,1725,1727,1729,1731],{"class":144,"line":188},[142,1724,172],{"class":152},[142,1726,175],{"class":148},[142,1728,178],{"class":152},[142,1730,182],{"class":181},[142,1732,185],{"class":152},[142,1734,1735,1737,1739,1741,1743,1745,1747,1750,1753,1756,1758,1760,1763,1765],{"class":144,"line":200},[142,1736,447],{"class":152},[142,1738,175],{"class":148},[142,1740,227],{"class":152},[142,1742,230],{"class":148},[142,1744,233],{"class":181},[142,1746,563],{"class":196},[142,1748,1749],{"class":181},"ata",[142,1751,1752],{"class":236},"\\o",[142,1754,1755],{"class":181},"rders",[142,1757,249],{"class":196},[142,1759,252],{"class":181},[142,1761,1762],{"class":152},").Sheets(",[142,1764,1443],{"class":196},[142,1766,185],{"class":152},[142,1768,1769,1771,1774],{"class":144,"line":210},[142,1770,497],{"class":152},[142,1772,1773],{"class":181},"\"A1:D500\"",[142,1775,773],{"class":152},[142,1777,1778,1780,1782,1784,1786],{"class":144,"line":219},[142,1779,778],{"class":308},[142,1781,175],{"class":148},[142,1783,497],{"class":152},[142,1785,785],{"class":181},[142,1787,1788],{"class":152},"),\n",[142,1790,1791,1794,1796],{"class":144,"line":257},[142,1792,1793],{"class":308},"    Order1",[142,1795,175],{"class":148},[142,1797,1798],{"class":152},"constants.xlAscending,\n",[142,1800,1801,1804,1806],{"class":144,"line":265},[142,1802,1803],{"class":308},"    Header",[142,1805,175],{"class":148},[142,1807,1808],{"class":152},"constants.xlYes,\n",[142,1810,1811],{"class":144,"line":288},[142,1812,185],{"class":152},[10,1814,1815,1816,1819,1820,1822,1823,1826],{},"Without the cache, ",[124,1817,1818],{},"constants.xlAscending"," raises and you are back to remembering that ascending is\n",[124,1821,1443],{},". The cache is written under the user's temporary directory the first time and reused after that;\nif Excel is upgraded and the cached module goes stale, deleting the ",[124,1824,1825],{},"gen_py"," folder regenerates it —\nwhich is the fix for the otherwise baffling \"This COM object can not automate the makepy process\"\nerror.",[10,1828,1829,1830,1833,1834,1838],{},"COM exceptions themselves arrive as ",[124,1831,1832],{},"pywintypes.com_error"," with a tuple of numbers rather than a\nmessage. The second element is usually the readable part, and the fourth carries Excel's own\ndescription when there is one — decoding it turns an opaque failure into something loggable, which\n",[14,1835,1837],{"href":1836},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fhandle-com-errors-and-excel-dialog-prompts-in-python\u002F","Handle COM Errors and Excel Dialog Prompts in Python","\ncovers in full.",[116,1840,1842],{"id":1841},"charts-shapes-and-images","Charts, shapes and images",[10,1844,1845],{},"Chart objects are one of the places where the file-level libraries and Excel disagree most: openpyxl\ncan create a chart but not render one, and it drops several chart types entirely on a round trip.\nThrough COM you get Excel's own chart engine, including the ability to export a chart as an image\nfor use somewhere else.",[133,1847,1849],{"className":135,"code":1848,"language":137,"meta":138,"style":138},"XL_LINE, XL_COLUMN_CLUSTERED = 4, 51\n\nsheet = book.Sheets(\"Data\")\nchart_object = sheet.ChartObjects().Add(Left=420, Top=20, Width=460, Height=280)\nchart = chart_object.Chart\nchart.ChartType = XL_LINE\nchart.SetSourceData(sheet.Range(\"A1:B37\"))\nchart.HasTitle = True\nchart.ChartTitle.Text = \"Monthly revenue\"\nchart.Export(r\"C:\\reports\\revenue.png\")        # a real rendered image\n",[124,1850,1851,1871,1875,1887,1936,1946,1956,1967,1976,1986],{"__ignoreMap":138},[142,1852,1853,1856,1858,1861,1863,1866,1868],{"class":144,"line":145},[142,1854,1855],{"class":196},"XL_LINE",[142,1857,584],{"class":152},[142,1859,1860],{"class":196},"XL_COLUMN_CLUSTERED",[142,1862,743],{"class":148},[142,1864,1865],{"class":196}," 4",[142,1867,584],{"class":152},[142,1869,1870],{"class":196},"51\n",[142,1872,1873],{"class":144,"line":162},[142,1874,166],{"emptyLinePlaceholder":165},[142,1876,1877,1879,1881,1883,1885],{"class":144,"line":169},[142,1878,447],{"class":152},[142,1880,175],{"class":148},[142,1882,452],{"class":152},[142,1884,455],{"class":181},[142,1886,185],{"class":152},[142,1888,1889,1892,1894,1897,1900,1902,1905,1907,1910,1912,1914,1916,1919,1921,1924,1926,1929,1931,1934],{"class":144,"line":188},[142,1890,1891],{"class":152},"chart_object ",[142,1893,175],{"class":148},[142,1895,1896],{"class":152}," sheet.ChartObjects().Add(",[142,1898,1899],{"class":308},"Left",[142,1901,175],{"class":148},[142,1903,1904],{"class":196},"420",[142,1906,584],{"class":152},[142,1908,1909],{"class":308},"Top",[142,1911,175],{"class":148},[142,1913,372],{"class":196},[142,1915,584],{"class":152},[142,1917,1918],{"class":308},"Width",[142,1920,175],{"class":148},[142,1922,1923],{"class":196},"460",[142,1925,584],{"class":152},[142,1927,1928],{"class":308},"Height",[142,1930,175],{"class":148},[142,1932,1933],{"class":196},"280",[142,1935,185],{"class":152},[142,1937,1938,1941,1943],{"class":144,"line":200},[142,1939,1940],{"class":152},"chart ",[142,1942,175],{"class":148},[142,1944,1945],{"class":152}," chart_object.Chart\n",[142,1947,1948,1951,1953],{"class":144,"line":210},[142,1949,1950],{"class":152},"chart.ChartType ",[142,1952,175],{"class":148},[142,1954,1955],{"class":196}," XL_LINE\n",[142,1957,1958,1961,1964],{"class":144,"line":219},[142,1959,1960],{"class":152},"chart.SetSourceData(sheet.Range(",[142,1962,1963],{"class":181},"\"A1:B37\"",[142,1965,1966],{"class":152},"))\n",[142,1968,1969,1972,1974],{"class":144,"line":257},[142,1970,1971],{"class":152},"chart.HasTitle ",[142,1973,175],{"class":148},[142,1975,1265],{"class":196},[142,1977,1978,1981,1983],{"class":144,"line":265},[142,1979,1980],{"class":152},"chart.ChartTitle.Text ",[142,1982,175],{"class":148},[142,1984,1985],{"class":181}," \"Monthly revenue\"\n",[142,1987,1988,1991,1993,1995,1997,1999,2001,2004,2006,2009,2011],{"class":144,"line":288},[142,1989,1990],{"class":152},"chart.Export(",[142,1992,230],{"class":148},[142,1994,233],{"class":181},[142,1996,237],{"class":236},[142,1998,240],{"class":181},[142,2000,237],{"class":236},[142,2002,2003],{"class":181},"evenue",[142,2005,249],{"class":196},[142,2007,2008],{"class":181},"png\"",[142,2010,1253],{"class":152},[142,2012,2013],{"class":466},"# a real rendered image\n",[10,2015,2016,2019,2020,249],{},[124,2017,2018],{},"Chart.Export"," has no equivalent anywhere in the pure-Python ecosystem — it is Excel drawing the\nchart and saving the pixels. That makes it the practical route when a chart has to appear in an\nemail body or a slide rather than in a workbook, alongside the matplotlib approach in\n",[14,2021,2023],{"href":2022},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-a-matplotlib-chart-in-an-excel-report\u002F","Embed a Matplotlib Chart in an Excel Report",[10,2025,2026,2027,584,2030,2033],{},"The same object model covers shapes, text boxes and images, and the naming follows Excel's own:\n",[124,2028,2029],{},"sheet.Shapes.AddPicture(...)",[124,2031,2032],{},"sheet.Shapes(\"Logo\").Delete()",". Anything you can do by hand and\nrecord as a macro is available here under exactly the name the recorder produced.",[116,2035,2037],{"id":2036},"where-this-should-not-run","Where this should not run",[10,2039,2040],{},"It is worth being blunt about deployment, because the failure mode is expensive. Microsoft does not\nsupport Office automation from a service, a scheduled task under a non-interactive account, or a web\nserver process. Excel expects a desktop session: without one it may work for weeks and then hang on\na first-run dialog, a licence prompt or a repair message that no one can dismiss, and the symptom is\na job that stops producing output without producing an error either.",[10,2042,2043,2044,249],{},"The workable arrangements are an interactive workstation that a person logs into, or a dedicated\nWindows machine with a logged-in session and the script driven by a task that runs in that session.\nAnything else — a container, a Linux runner, a cloud function — belongs to openpyxl and xlsxwriter,\nwhich is the argument set out in\n",[14,2045,2047],{"href":2046},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Frun-a-python-excel-report-in-docker\u002F","Run a Python Excel Report in Docker",[10,2049,2050],{},"A useful design rule follows from that: keep the COM-dependent step as small and as late as\npossible. Build the workbook with file-level libraries, and use Excel only for the one operation\nthat needs it — the recalculation, the macro, the PDF — so that when the automation host eventually\nbecomes a problem, the part that has to move is a dozen lines rather than the whole job.",[116,2052,2054],{"id":2053},"a-worked-example-refresh-recalculate-export","A worked example: refresh, recalculate, export",[10,2056,2057],{},"Putting the pieces together, a realistic COM job is short. It opens a model somebody else maintains,\nlets Excel do the two things only Excel can do, and hands the result to the rest of the pipeline as\nan ordinary file.",[133,2059,2061],{"className":135,"code":2060,"language":137,"meta":138,"style":138},"import pythoncom\nimport win32com.client as win32\n\ndef publish(model_path: str, pdf_path: str) -> None:\n    pythoncom.CoInitialize()\n    excel = win32.DispatchEx(\"Excel.Application\")\n    excel.Visible = False\n    excel.DisplayAlerts = False\n    excel.AskToUpdateLinks = False\n    try:\n        book = excel.Workbooks.Open(model_path, UpdateLinks=0, ReadOnly=False)\n        try:\n            book.RefreshAll()\n            excel.CalculateUntilAsyncQueriesDone()\n            book.Save()\n            book.Sheets(\"Board summary\").ExportAsFixedFormat(0, pdf_path)\n        finally:\n            book.Close(SaveChanges=False)\n    finally:\n        excel.Quit()\n        pythoncom.CoUninitialize()\n\npublish(r\"C:\\finance\\model.xlsm\", r\"C:\\reports\\board-2026-Q3.pdf\")\n",[124,2062,2063,2069,2079,2083,2108,2112,2124,2132,2140,2149,2155,2182,2189,2194,2199,2204,2220,2227,2241,2248,2253,2258,2263],{"__ignoreMap":138},[142,2064,2065,2067],{"class":144,"line":145},[142,2066,149],{"class":148},[142,2068,862],{"class":152},[142,2070,2071,2073,2075,2077],{"class":144,"line":162},[142,2072,149],{"class":148},[142,2074,153],{"class":152},[142,2076,156],{"class":148},[142,2078,159],{"class":152},[142,2080,2081],{"class":144,"line":169},[142,2082,166],{"emptyLinePlaceholder":165},[142,2084,2085,2087,2090,2093,2096,2099,2101,2104,2106],{"class":144,"line":188},[142,2086,881],{"class":148},[142,2088,2089],{"class":884}," publish",[142,2091,2092],{"class":152},"(model_path: ",[142,2094,2095],{"class":196},"str",[142,2097,2098],{"class":152},", pdf_path: ",[142,2100,2095],{"class":196},[142,2102,2103],{"class":152},") -> ",[142,2105,1314],{"class":196},[142,2107,216],{"class":152},[142,2109,2110],{"class":144,"line":200},[142,2111,893],{"class":152},[142,2113,2114,2116,2118,2120,2122],{"class":144,"line":210},[142,2115,898],{"class":152},[142,2117,175],{"class":148},[142,2119,1248],{"class":152},[142,2121,182],{"class":181},[142,2123,185],{"class":152},[142,2125,2126,2128,2130],{"class":144,"line":219},[142,2127,911],{"class":152},[142,2129,175],{"class":148},[142,2131,197],{"class":196},[142,2133,2134,2136,2138],{"class":144,"line":257},[142,2135,920],{"class":152},[142,2137,175],{"class":148},[142,2139,197],{"class":196},[142,2141,2142,2145,2147],{"class":144,"line":265},[142,2143,2144],{"class":152},"    excel.AskToUpdateLinks ",[142,2146,175],{"class":148},[142,2148,197],{"class":196},[142,2150,2151,2153],{"class":144,"line":288},[142,2152,260],{"class":148},[142,2154,216],{"class":152},[142,2156,2157,2159,2161,2164,2167,2169,2171,2173,2176,2178,2180],{"class":144,"line":294},[142,2158,1337],{"class":152},[142,2160,175],{"class":148},[142,2162,2163],{"class":152}," excel.Workbooks.Open(model_path, ",[142,2165,2166],{"class":308},"UpdateLinks",[142,2168,175],{"class":148},[142,2170,41],{"class":196},[142,2172,584],{"class":152},[142,2174,2175],{"class":308},"ReadOnly",[142,2177,175],{"class":148},[142,2179,314],{"class":196},[142,2181,185],{"class":152},[142,2183,2184,2187],{"class":144,"line":302},[142,2185,2186],{"class":148},"        try",[142,2188,216],{"class":152},[142,2190,2191],{"class":144,"line":319},[142,2192,2193],{"class":152},"            book.RefreshAll()\n",[142,2195,2196],{"class":144,"line":326},[142,2197,2198],{"class":152},"            excel.CalculateUntilAsyncQueriesDone()\n",[142,2200,2201],{"class":144,"line":1384},[142,2202,2203],{"class":152},"            book.Save()\n",[142,2205,2206,2209,2212,2215,2217],{"class":144,"line":1391},[142,2207,2208],{"class":152},"            book.Sheets(",[142,2210,2211],{"class":181},"\"Board summary\"",[142,2213,2214],{"class":152},").ExportAsFixedFormat(",[142,2216,41],{"class":196},[142,2218,2219],{"class":152},", pdf_path)\n",[142,2221,2222,2225],{"class":144,"line":1399},[142,2223,2224],{"class":148},"        finally",[142,2226,216],{"class":152},[142,2228,2230,2233,2235,2237,2239],{"class":144,"line":2229},18,[142,2231,2232],{"class":152},"            book.Close(",[142,2234,309],{"class":308},[142,2236,175],{"class":148},[142,2238,314],{"class":196},[142,2240,185],{"class":152},[142,2242,2244,2246],{"class":144,"line":2243},19,[142,2245,297],{"class":148},[142,2247,216],{"class":152},[142,2249,2251],{"class":144,"line":2250},20,[142,2252,949],{"class":152},[142,2254,2256],{"class":144,"line":2255},21,[142,2257,962],{"class":152},[142,2259,2261],{"class":144,"line":2260},22,[142,2262,166],{"emptyLinePlaceholder":165},[142,2264,2266,2269,2271,2273,2275,2277,2279,2281,2283,2285,2287,2289,2291,2293,2295,2297,2300,2302,2304],{"class":144,"line":2265},23,[142,2267,2268],{"class":152},"publish(",[142,2270,230],{"class":148},[142,2272,233],{"class":181},[142,2274,1348],{"class":236},[142,2276,1351],{"class":181},[142,2278,243],{"class":236},[142,2280,246],{"class":181},[142,2282,249],{"class":196},[142,2284,571],{"class":181},[142,2286,584],{"class":152},[142,2288,230],{"class":148},[142,2290,233],{"class":181},[142,2292,237],{"class":236},[142,2294,240],{"class":181},[142,2296,1354],{"class":196},[142,2298,2299],{"class":181},"oard-2026-Q3",[142,2301,249],{"class":196},[142,2303,1662],{"class":181},[142,2305,185],{"class":152},[10,2307,2308,2311,2312,2315,2316,2318],{},[124,2309,2310],{},"AskToUpdateLinks = False"," and ",[124,2313,2314],{},"UpdateLinks=0"," together suppress the prompt a workbook with external\nreferences raises on open — another dialog that hangs an unattended run. The nested ",[124,2317,213],{}," blocks are\ndeliberate: the workbook closes even if the export fails, and the application quits even if the\nclose fails, which is what keeps the process count at zero after a bad night.",[10,2320,2321],{},"Everything after this point — attaching the PDF to an email, uploading it, logging the run — is\nordinary Python that no longer needs Excel at all. That boundary is where a COM script should end.",[116,2323,2325],{"id":2324},"key-takeaways","Key takeaways",[2327,2328,2329,2333,2336,2341,2346,2352],"ul",{},[2330,2331,2332],"li",{},"COM automation drives a running copy of Excel; it is Windows-only, needs a licensed installation,\nand has no headless mode.",[2330,2334,2335],{},"Cost is per call, not per cell — move whole ranges in one assignment and a slow script becomes a\nfast one.",[2330,2337,2338,2340],{},[124,2339,347],{}," prevents the invisible modal dialog that turns a failure into a hang.",[2330,2342,2343,2345],{},[124,2344,619],{}," starts a refresh rather than finishing it; wait for queries before saving.",[2330,2347,2348,2349,2351],{},"Excel only exits when every reference is released, so quit in a ",[124,2350,130],{}," and initialise COM\nexplicitly in any non-main thread.",[2330,2353,2354],{},"Recorded VBA is the documentation: the object model is identical, and the translation to Python\nis close to mechanical.",[116,2356,2358],{"id":2357},"frequently-asked-questions","Frequently asked questions",[10,2360,2361,2365],{},[2362,2363,2364],"strong",{},"What is the difference between pywin32 and xlwings?","\nxlwings is a friendly wrapper around the same COM interface pywin32 exposes. xlwings gives you Python-shaped objects, DataFrame conversion and a context manager; pywin32 gives you Excel's object model exactly as Microsoft documents it. Anything xlwings has not wrapped is reachable through its .api attribute, which is a pywin32 object.",[10,2367,2368,2371],{},[2362,2369,2370],{},"Does COM automation work on macOS or Linux?","\nNo. COM is a Windows technology. On macOS xlwings uses AppleScript instead, and on Linux there is no application to drive at all — use openpyxl or xlsxwriter there.",[10,2373,2374,2377],{},[2362,2375,2376],{},"Why does Excel stay running after my script ends?","\nBecause a COM reference is still held somewhere, or the script exited before Quit() was called. Excel only closes when every reference to it is released, which is why the pattern in this section always quits in a finally block.",[10,2379,2380,2383],{},[2362,2381,2382],{},"Can I run COM automation from a scheduled task or a service?","\nIt is possible and unsupported. Excel expects an interactive desktop session; under a service account it frequently hangs on a dialog nobody can dismiss. For anything scheduled, use the file-level libraries and keep COM for interactive machines.",[10,2385,2386,2389],{},[2362,2387,2388],{},"How do I find the right method name?","\nThe COM object model is the same one the VBA editor documents, so record a macro in Excel, read the generated VBA, and translate it almost line for line. Method and property names are identical; only the syntax changes.",[10,2391,2392,2395],{},[2362,2393,2394],{},"Do I need Excel installed for pywin32 itself?","\npywin32 installs fine without Excel, but the Excel.Application dispatch will fail at runtime with a class-not-registered error. The library is the bridge; Excel is what it bridges to.",[116,2397,2399],{"id":2398},"related","Related",[2327,2401,2402,2408,2415,2422,2427,2432,2437,2444],{},[2330,2403,2404,2405,2407],{},"Up one level: ",[14,2406,17],{"href":16}," — where application automation sits among the file-level libraries.",[2330,2409,2410,2414],{},[14,2411,2413],{"href":2412},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fread-and-write-cell-ranges-with-win32com\u002F","Read and Write Cell Ranges with win32com"," — moving blocks of data across the boundary efficiently.",[2330,2416,2417,2421],{},[14,2418,2420],{"href":2419},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frun-an-excel-macro-from-python-with-win32com\u002F","Run an Excel Macro from Python with win32com"," — calling existing VBA, with arguments and return values.",[2330,2423,2424,2426],{},[14,2425,624],{"href":623}," — queries, pivot caches and the wait that stops a stale save.",[2330,2428,2429,2431],{},[14,2430,972],{"href":971}," — the lifecycle rules that keep EXCEL.EXE from accumulating.",[2330,2433,2434,2436],{},[14,2435,1837],{"href":1836}," — decoding COM exceptions and stopping the invisible-dialog hang.",[2330,2438,2439,2443],{},[14,2440,2442],{"href":2441},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fxlwings-vs-pywin32-for-excel-automation\u002F","xlwings vs pywin32 for Excel Automation"," — the wrapper against the raw interface, feature by feature.",[2330,2445,2446,2450],{},[14,2447,2449],{"href":2448},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-xlwings-basics\u002F","Automating Excel with xlwings Basics"," — the friendlier API over the same capability.",[2452,2453,2454],"style",{},"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 .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html .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-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":138,"searchDepth":162,"depth":162,"links":2456},[2457,2458,2459,2460,2461,2462,2463,2464,2465,2466,2467,2468,2469,2470,2471],{"id":118,"depth":162,"text":119},{"id":351,"depth":162,"text":352},{"id":534,"depth":162,"text":535},{"id":628,"depth":162,"text":629},{"id":843,"depth":162,"text":844},{"id":976,"depth":162,"text":977},{"id":1065,"depth":162,"text":1066},{"id":1411,"depth":162,"text":1412},{"id":1682,"depth":162,"text":1683},{"id":1841,"depth":162,"text":1842},{"id":2036,"depth":162,"text":2037},{"id":2053,"depth":162,"text":2054},{"id":2324,"depth":162,"text":2325},{"id":2357,"depth":162,"text":2358},{"id":2398,"depth":162,"text":2399},"2026-09-04","Drive a real copy of Excel from Python: attach with win32com, move ranges in one call, run macros and refresh queries, and quit cleanly so no EXCEL.EXE is left behind.","md",[2476,2478,2480,2482,2484,2486],{"q":2364,"a":2477},"xlwings is a friendly wrapper around the same COM interface pywin32 exposes. xlwings gives you Python-shaped objects, DataFrame conversion and a context manager; pywin32 gives you Excel's object model exactly as Microsoft documents it. Anything xlwings has not wrapped is reachable through its .api attribute, which is a pywin32 object.",{"q":2370,"a":2479},"No. COM is a Windows technology. On macOS xlwings uses AppleScript instead, and on Linux there is no application to drive at all — use openpyxl or xlsxwriter there.",{"q":2376,"a":2481},"Because a COM reference is still held somewhere, or the script exited before Quit() was called. Excel only closes when every reference to it is released, which is why the pattern in this section always quits in a finally block.",{"q":2382,"a":2483},"It is possible and unsupported. Excel expects an interactive desktop session; under a service account it frequently hangs on a dialog nobody can dismiss. For anything scheduled, use the file-level libraries and keep COM for interactive machines.",{"q":2388,"a":2485},"The COM object model is the same one the VBA editor documents, so record a macro in Excel, read the generated VBA, and translate it almost line for line. Method and property names are identical; only the syntax changes.",{"q":2394,"a":2487},"pywin32 installs fine without Excel, but the Excel.Application dispatch will fail at runtime with a class-not-registered error. The library is the bridge; Excel is what it bridges to.",{"breadcrumb":2489},[2490,2493,2494],{"name":2491,"item":2492},"Home","\u002F",{"name":17,"item":16},{"name":5,"item":2495},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002F","\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32",{"title":5,"description":2498},"A practical guide to Excel COM automation in Python: EnsureDispatch, batching range reads and writes, running macros, refreshing queries, and avoiding orphan processes.","automating-excel-with-com-and-pywin32","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Findex","guide","7d3A-v4zWxO-j_Dj2uMHB2vLraP0eCdm66oeww_wY5Q",[2504,2507],{"title":17,"path":2505,"stem":2506,"children":-1},"\u002Fgetting-started-with-python-excel-automation","getting-started-with-python-excel-automation\u002Findex",{"title":972,"path":2508,"stem":2509,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fclose-excel-cleanly-and-avoid-orphan-com-processes","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fclose-excel-cleanly-and-avoid-orphan-com-processes\u002Findex",1788710151645]