[{"data":1,"prerenderedAt":1840},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fclose-excel-cleanly-and-avoid-orphan-com-processes":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fclose-excel-cleanly-and-avoid-orphan-com-processes":1833},{"id":4,"title":5,"body":6,"dateModified":1805,"datePublished":1805,"description":1806,"extension":1807,"faq":1808,"meta":1817,"navigation":212,"path":1826,"seo":1827,"slug":1829,"stem":1830,"type":1831,"__hash__":1832},"docs\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fclose-excel-cleanly-and-avoid-orphan-com-processes\u002Findex.md","Close Excel Cleanly and Avoid Orphan COM Processes",{"type":7,"value":8,"toc":1790},"minimark",[9,24,127,132,159,165,169,184,334,340,344,539,562,566,669,676,845,848,852,855,1000,1003,1007,1010,1114,1128,1132,1135,1145,1148,1152,1155,1330,1338,1342,1460,1464,1470,1597,1696,1699,1703,1715,1719,1726,1732,1738,1744,1748,1786],[10,11,12,13,17,18,23],"p",{},"Every other library on this site releases what it holds when a variable goes out of scope. COM does\nnot: ",[14,15,16],"code",{},"EXCEL.EXE"," stays alive while any reference to it exists anywhere in the process, and an\norphaned instance keeps a lock on whatever workbook it had open. On a machine running a report\nhourly, that becomes a dozen hidden Excel processes and a job that fails with a file-in-use error\nnobody can explain. This guide, part of\n",[19,20,22],"a",{"href":21},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002F","Automating Excel with COM and pywin32",",\ncovers the lifecycle rules that prevent it.",[25,26,34,35,34,39,34,43,34,50,34,57,34,67,34,73,34,78,34,85,34,90,34,95,34,99,34,102,34,106,34,109,34,114,34,119,34,122],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"0 0 760 232","img",[30,31],"orp-ref-t","orp-ref-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"Why Quit does not always quit",[40,41,42],"desc",{"id":31},"Excel exits only when the reference count reaches zero, so a worksheet object still held in Python keeps the hidden process alive after Quit returns.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","232","#ffffff",[51,52,56],"text",{"x":53,"y":54,"style":55},"380.0","32","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","the reference count decides",[44,58],{"x":59,"y":60,"width":61,"height":62,"rx":63,"fill":64,"stroke":65,"style":66},"24.0","74","208.0","96","12","#fdefd8","var(--gold,#b4740a)","stroke-width:2px",[51,68,72],{"x":69,"y":70,"style":71},"128.0","114","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Quit() called",[51,74,77],{"x":69,"y":75,"style":76},"136","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","asks Excel to close",[79,80],"line",{"x1":81,"y1":82,"x2":83,"y2":82,"stroke":84,"style":66},"237.0","122.0","269.0","var(--brand,#5b5cf0)",[86,87],"polygon",{"points":88,"fill":89},"269.0,122.0 260.0,117.0 260.0,127.0","#5b5cf0",[44,91],{"x":92,"y":60,"width":61,"height":62,"rx":63,"fill":93,"stroke":94,"style":66},"276.0","#fee8f2","var(--accent,#d81b73)",[51,96,98],{"x":53,"y":70,"style":97},"font-size:14px;font-weight:700;fill:var(--accent,#d81b73);text-anchor:middle","a reference remains",[51,100,101],{"x":53,"y":75,"style":76},"a sheet or book object",[79,103],{"x1":104,"y1":82,"x2":105,"y2":82,"stroke":84,"style":66},"489.0","521.0",[86,107],{"points":108,"fill":89},"521.0,122.0 512.0,117.0 512.0,127.0",[44,110],{"x":111,"y":60,"width":61,"height":62,"rx":63,"fill":112,"stroke":113,"style":66},"528.0","#e7ebef","var(--line,#cdd5e6)",[51,115,118],{"x":116,"y":70,"style":117},"632.0","font-size:14px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","EXCEL.EXE lives on",[51,120,121],{"x":116,"y":75,"style":76},"hidden, holding a lock",[51,123,126],{"x":53,"y":124,"style":125},"210","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","if a call still works after Quit, the process never closed",[128,129,131],"h2",{"id":130},"prerequisites","Prerequisites",[133,134,139],"pre",{"className":135,"code":136,"language":137,"meta":138,"style":138},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pywin32 psutil\n","bash","",[14,140,141],{"__ignoreMap":138},[142,143,145,149,153,156],"span",{"class":79,"line":144},1,[142,146,148],{"class":147},"sMTad","pip",[142,150,152],{"class":151},"srMev"," install",[142,154,155],{"class":151}," pywin32",[142,157,158],{"class":151}," psutil\n",[10,160,161,164],{},[14,162,163],{},"psutil"," is only needed for the detection and last-resort cleanup sections at the end.",[128,166,168],{"id":167},"why-quit-sometimes-does-nothing","Why Quit() sometimes does nothing",[10,170,171,172,175,176,179,180,183],{},"Excel exits when its reference count reaches zero. ",[14,173,174],{},"Quit()"," asks it to close, but if a Python\nvariable still points at a workbook, a worksheet or a range inside that application, the count is\nnot zero and the process remains — invisible, because ",[14,177,178],{},"Visible"," was set to ",[14,181,182],{},"False",".",[133,185,189],{"className":186,"code":187,"language":188,"meta":138,"style":138},"language-python shiki shiki-themes github-light github-dark-high-contrast","import win32com.client as win32\n\nexcel = win32.DispatchEx(\"Excel.Application\")\nexcel.Visible = False\nbook = excel.Workbooks.Open(r\"C:\\data\\orders.xlsx\")\nsheet = book.Sheets(\"Data\")          # a live reference into the application\n\nexcel.Quit()                         # returns, but EXCEL.EXE is still running\nprint(sheet.Range(\"A1\").Value)       # ...and this still works, which proves it\n","python",[14,190,191,207,214,232,244,281,302,307,316],{"__ignoreMap":138},[142,192,193,197,201,204],{"class":79,"line":144},[142,194,196],{"class":195},"s-kum","import",[142,198,200],{"class":199},"skGVy"," win32com.client ",[142,202,203],{"class":195},"as",[142,205,206],{"class":199}," win32\n",[142,208,210],{"class":79,"line":209},2,[142,211,213],{"emptyLinePlaceholder":212},true,"\n",[142,215,217,220,223,226,229],{"class":79,"line":216},3,[142,218,219],{"class":199},"excel ",[142,221,222],{"class":195},"=",[142,224,225],{"class":199}," win32.DispatchEx(",[142,227,228],{"class":151},"\"Excel.Application\"",[142,230,231],{"class":199},")\n",[142,233,235,238,240],{"class":79,"line":234},4,[142,236,237],{"class":199},"excel.Visible ",[142,239,222],{"class":195},[142,241,243],{"class":242},"sP0c6"," False\n",[142,245,247,250,252,255,258,261,264,267,271,274,276,279],{"class":79,"line":246},5,[142,248,249],{"class":199},"book ",[142,251,222],{"class":195},[142,253,254],{"class":199}," excel.Workbooks.Open(",[142,256,257],{"class":195},"r",[142,259,260],{"class":151},"\"C:",[142,262,263],{"class":242},"\\d",[142,265,266],{"class":151},"ata",[142,268,270],{"class":269},"s_b0D","\\o",[142,272,273],{"class":151},"rders",[142,275,183],{"class":242},[142,277,278],{"class":151},"xlsx\"",[142,280,231],{"class":199},[142,282,284,287,289,292,295,298],{"class":79,"line":283},6,[142,285,286],{"class":199},"sheet ",[142,288,222],{"class":195},[142,290,291],{"class":199}," book.Sheets(",[142,293,294],{"class":151},"\"Data\"",[142,296,297],{"class":199},")          ",[142,299,301],{"class":300},"s-wDw","# a live reference into the application\n",[142,303,305],{"class":79,"line":304},7,[142,306,213],{"emptyLinePlaceholder":212},[142,308,310,313],{"class":79,"line":309},8,[142,311,312],{"class":199},"excel.Quit()                         ",[142,314,315],{"class":300},"# returns, but EXCEL.EXE is still running\n",[142,317,319,322,325,328,331],{"class":79,"line":318},9,[142,320,321],{"class":242},"print",[142,323,324],{"class":199},"(sheet.Range(",[142,326,327],{"class":151},"\"A1\"",[142,329,330],{"class":199},").Value)       ",[142,332,333],{"class":300},"# ...and this still works, which proves it\n",[10,335,336,337,339],{},"That last line is the tell. If a call succeeds after ",[14,338,174],{},", the application never closed. The\nfix is to release references in the reverse of the order you acquired them, innermost first.",[128,341,343],{"id":342},"the-shape-that-always-cleans-up","The shape that always cleans up",[133,345,347],{"className":186,"code":346,"language":188,"meta":138,"style":138},"import pythoncom\nimport win32com.client as win32\n\ndef with_workbook(path, work):\n    pythoncom.CoInitialize()\n    excel = win32.DispatchEx(\"Excel.Application\")\n    excel.Visible = False\n    excel.DisplayAlerts = False\n    book = None\n    try:\n        book = excel.Workbooks.Open(path, UpdateLinks=0)\n        return work(book)\n    finally:\n        if book is not None:\n            book.Close(SaveChanges=False)\n        excel.Quit()\n        del book\n        del excel\n        pythoncom.CoUninitialize()\n",[14,348,349,356,366,370,382,387,400,409,418,428,437,458,467,475,495,510,516,525,533],{"__ignoreMap":138},[142,350,351,353],{"class":79,"line":144},[142,352,196],{"class":195},[142,354,355],{"class":199}," pythoncom\n",[142,357,358,360,362,364],{"class":79,"line":209},[142,359,196],{"class":195},[142,361,200],{"class":199},[142,363,203],{"class":195},[142,365,206],{"class":199},[142,367,368],{"class":79,"line":216},[142,369,213],{"emptyLinePlaceholder":212},[142,371,372,375,379],{"class":79,"line":234},[142,373,374],{"class":195},"def",[142,376,378],{"class":377},"s_Opv"," with_workbook",[142,380,381],{"class":199},"(path, work):\n",[142,383,384],{"class":79,"line":246},[142,385,386],{"class":199},"    pythoncom.CoInitialize()\n",[142,388,389,392,394,396,398],{"class":79,"line":283},[142,390,391],{"class":199},"    excel ",[142,393,222],{"class":195},[142,395,225],{"class":199},[142,397,228],{"class":151},[142,399,231],{"class":199},[142,401,402,405,407],{"class":79,"line":304},[142,403,404],{"class":199},"    excel.Visible ",[142,406,222],{"class":195},[142,408,243],{"class":242},[142,410,411,414,416],{"class":79,"line":309},[142,412,413],{"class":199},"    excel.DisplayAlerts ",[142,415,222],{"class":195},[142,417,243],{"class":242},[142,419,420,423,425],{"class":79,"line":318},[142,421,422],{"class":199},"    book ",[142,424,222],{"class":195},[142,426,427],{"class":242}," None\n",[142,429,431,434],{"class":79,"line":430},10,[142,432,433],{"class":195},"    try",[142,435,436],{"class":199},":\n",[142,438,440,443,445,448,452,454,456],{"class":79,"line":439},11,[142,441,442],{"class":199},"        book ",[142,444,222],{"class":195},[142,446,447],{"class":199}," excel.Workbooks.Open(path, ",[142,449,451],{"class":450},"sa561","UpdateLinks",[142,453,222],{"class":195},[142,455,46],{"class":242},[142,457,231],{"class":199},[142,459,461,464],{"class":79,"line":460},12,[142,462,463],{"class":195},"        return",[142,465,466],{"class":199}," work(book)\n",[142,468,470,473],{"class":79,"line":469},13,[142,471,472],{"class":195},"    finally",[142,474,436],{"class":199},[142,476,478,481,484,487,490,493],{"class":79,"line":477},14,[142,479,480],{"class":195},"        if",[142,482,483],{"class":199}," book ",[142,485,486],{"class":195},"is",[142,488,489],{"class":195}," not",[142,491,492],{"class":242}," None",[142,494,436],{"class":199},[142,496,498,501,504,506,508],{"class":79,"line":497},15,[142,499,500],{"class":199},"            book.Close(",[142,502,503],{"class":450},"SaveChanges",[142,505,222],{"class":195},[142,507,182],{"class":242},[142,509,231],{"class":199},[142,511,513],{"class":79,"line":512},16,[142,514,515],{"class":199},"        excel.Quit()\n",[142,517,519,522],{"class":79,"line":518},17,[142,520,521],{"class":195},"        del",[142,523,524],{"class":199}," book\n",[142,526,528,530],{"class":79,"line":527},18,[142,529,521],{"class":195},[142,531,532],{"class":199}," excel\n",[142,534,536],{"class":79,"line":535},19,[142,537,538],{"class":199},"        pythoncom.CoUninitialize()\n",[10,540,541,542,545,546,549,550,553,554,557,558,561],{},"Four details do the work. ",[14,543,544],{},"DispatchEx"," starts a private instance, so quitting it cannot close a\nworkbook somebody was using. The ",[14,547,548],{},"finally"," runs whether or not ",[14,551,552],{},"work"," raised. The explicit ",[14,555,556],{},"del","\nstatements drop the last Python references immediately rather than waiting for garbage collection.\nAnd ",[14,559,560],{},"CoUninitialize"," tears down the COM apartment for the thread, which is what actually lets the\nprocess go.",[128,563,565],{"id":564},"threads-servers-and-coinitialize","Threads, servers and CoInitialize",[25,567,34,572,34,575,34,578,34,581,34,590,34,595,34,600,34,606,34,611,34,614,34,617,34,621,34,625,34,629,34,632,34,635,34,639,34,643,34,647,34,650,34,653,34,657,34,661,34,665],{"viewBox":568,"role":28,"ariaLabelledBy":569,"xmlns":32,"style":33},"0 0 760 308",[570,571],"orp-thread-t","orp-thread-d",[36,573,574],{"id":570},"COM initialisation is per thread, not per process",[40,576,577],{"id":571},"The main thread of a script usually has COM initialised implicitly, but a worker thread, a web request handler or a scheduler job must call CoInitialize itself and pair it with CoUninitialize.",[44,579],{"x":46,"y":46,"width":47,"height":580,"fill":49},"308",[44,582],{"x":583,"y":584,"width":585,"height":586,"rx":587,"fill":588,"stroke":589},"22","28","716","52","10","#d9f4f1","var(--teal,#0f9488)",[591,592],"circle",{"cx":586,"cy":593,"r":594,"fill":89},"54.0","15",[51,596,599],{"x":586,"y":597,"style":598},"59.0","font-size:13px;font-weight:700;fill:#ffffff;text-anchor:middle","1",[51,601,605],{"x":602,"y":603,"style":604},"82","50","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","Main thread of a script",[51,607,610],{"x":602,"y":608,"style":609},"68","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:start","usually initialised implicitly",[44,612],{"x":583,"y":613,"width":585,"height":586,"rx":587,"fill":588,"stroke":589},"90",[591,615],{"cx":586,"cy":616,"r":594,"fill":89},"116.0",[51,618,620],{"x":586,"y":619,"style":598},"121.0","2",[51,622,624],{"x":602,"y":623,"style":604},"112","Worker or scheduler thread",[51,626,628],{"x":602,"y":627,"style":609},"130","must call CoInitialize itself",[44,630],{"x":583,"y":631,"width":585,"height":586,"rx":587,"fill":588,"stroke":589},"152",[591,633],{"cx":586,"cy":634,"r":594,"fill":89},"178.0",[51,636,638],{"x":586,"y":637,"style":598},"183.0","3",[51,640,642],{"x":602,"y":641,"style":604},"174","Web request handler",[51,644,646],{"x":602,"y":645,"style":609},"192","same rule — each request thread",[44,648],{"x":583,"y":649,"width":585,"height":586,"rx":587,"fill":588,"stroke":589},"214",[591,651],{"cx":586,"cy":652,"r":594,"fill":89},"240.0",[51,654,656],{"x":586,"y":655,"style":598},"245.0","4",[51,658,660],{"x":602,"y":659,"style":604},"236","Always pair it",[51,662,664],{"x":602,"y":663,"style":609},"254","CoUninitialize in the finally block",[51,666,668],{"x":53,"y":667,"style":125},"296","the error names CoInitialize and never mentions threads",[10,670,671,672,675],{},"COM is initialised per thread. A plain script's main thread usually has it done implicitly, but any\nother thread does not — and the failure is an error mentioning ",[14,673,674],{},"CoInitialize"," that says nothing\nabout threads at all.",[133,677,679],{"className":186,"code":678,"language":188,"meta":138,"style":138},"import threading\nimport pythoncom\nimport win32com.client as win32\n\ndef worker(path):\n    pythoncom.CoInitialize()             # required in this thread\n    try:\n        excel = win32.DispatchEx(\"Excel.Application\")\n        try:\n            book = excel.Workbooks.Open(path)\n            book.RefreshAll()\n            book.Close(SaveChanges=True)\n        finally:\n            excel.Quit()\n    finally:\n        pythoncom.CoUninitialize()\n\nthreading.Thread(target=worker, args=(r\"C:\\data\\a.xlsx\",)).start()\n",[14,680,681,688,694,704,708,718,726,732,745,752,762,767,780,787,792,798,802,806],{"__ignoreMap":138},[142,682,683,685],{"class":79,"line":144},[142,684,196],{"class":195},[142,686,687],{"class":199}," threading\n",[142,689,690,692],{"class":79,"line":209},[142,691,196],{"class":195},[142,693,355],{"class":199},[142,695,696,698,700,702],{"class":79,"line":216},[142,697,196],{"class":195},[142,699,200],{"class":199},[142,701,203],{"class":195},[142,703,206],{"class":199},[142,705,706],{"class":79,"line":234},[142,707,213],{"emptyLinePlaceholder":212},[142,709,710,712,715],{"class":79,"line":246},[142,711,374],{"class":195},[142,713,714],{"class":377}," worker",[142,716,717],{"class":199},"(path):\n",[142,719,720,723],{"class":79,"line":283},[142,721,722],{"class":199},"    pythoncom.CoInitialize()             ",[142,724,725],{"class":300},"# required in this thread\n",[142,727,728,730],{"class":79,"line":304},[142,729,433],{"class":195},[142,731,436],{"class":199},[142,733,734,737,739,741,743],{"class":79,"line":309},[142,735,736],{"class":199},"        excel ",[142,738,222],{"class":195},[142,740,225],{"class":199},[142,742,228],{"class":151},[142,744,231],{"class":199},[142,746,747,750],{"class":79,"line":318},[142,748,749],{"class":195},"        try",[142,751,436],{"class":199},[142,753,754,757,759],{"class":79,"line":430},[142,755,756],{"class":199},"            book ",[142,758,222],{"class":195},[142,760,761],{"class":199}," excel.Workbooks.Open(path)\n",[142,763,764],{"class":79,"line":439},[142,765,766],{"class":199},"            book.RefreshAll()\n",[142,768,769,771,773,775,778],{"class":79,"line":460},[142,770,500],{"class":199},[142,772,503],{"class":450},[142,774,222],{"class":195},[142,776,777],{"class":242},"True",[142,779,231],{"class":199},[142,781,782,785],{"class":79,"line":469},[142,783,784],{"class":195},"        finally",[142,786,436],{"class":199},[142,788,789],{"class":79,"line":477},[142,790,791],{"class":199},"            excel.Quit()\n",[142,793,794,796],{"class":79,"line":497},[142,795,472],{"class":195},[142,797,436],{"class":199},[142,799,800],{"class":79,"line":512},[142,801,538],{"class":199},[142,803,804],{"class":79,"line":518},[142,805,213],{"emptyLinePlaceholder":212},[142,807,808,811,814,816,819,822,824,827,829,831,833,835,838,840,842],{"class":79,"line":527},[142,809,810],{"class":199},"threading.Thread(",[142,812,813],{"class":450},"target",[142,815,222],{"class":195},[142,817,818],{"class":199},"worker, ",[142,820,821],{"class":450},"args",[142,823,222],{"class":195},[142,825,826],{"class":199},"(",[142,828,257],{"class":195},[142,830,260],{"class":151},[142,832,263],{"class":242},[142,834,266],{"class":151},[142,836,837],{"class":269},"\\a",[142,839,183],{"class":242},[142,841,278],{"class":151},[142,843,844],{"class":199},",)).start()\n",[10,846,847],{},"The same requirement applies inside a Flask or FastAPI request handler, an APScheduler job, and a\nCelery worker — anywhere the code runs on a thread the interpreter created rather than the one it\nstarted on. Running two Excel instances concurrently from different threads works, but it is worth\nnoting that Excel is not designed for it and mysterious failures under concurrency are common;\nserialising the work with a lock is usually the calmer choice.",[128,849,851],{"id":850},"detecting-the-orphans-you-already-have","Detecting the orphans you already have",[10,853,854],{},"Before adding cleanup, it is worth measuring the problem. Counting hidden Excel processes takes\nthree lines and often produces a surprising number on a machine that has been running scheduled\njobs for a while.",[133,856,858],{"className":186,"code":857,"language":188,"meta":138,"style":138},"import psutil\n\norphans = [p for p in psutil.process_iter([\"pid\", \"name\", \"create_time\"])\n           if p.info[\"name\"] and p.info[\"name\"].lower() == \"excel.exe\"]\nfor process in orphans:\n    print(process.info[\"pid\"], process.info[\"create_time\"])\nprint(f\"{len(orphans)} Excel process(es) running\")\n",[14,859,860,866,870,909,941,953,970],{"__ignoreMap":138},[142,861,862,864],{"class":79,"line":144},[142,863,196],{"class":195},[142,865,158],{"class":199},[142,867,868],{"class":79,"line":209},[142,869,213],{"emptyLinePlaceholder":212},[142,871,872,875,877,880,883,886,889,892,895,898,901,903,906],{"class":79,"line":216},[142,873,874],{"class":199},"orphans ",[142,876,222],{"class":195},[142,878,879],{"class":199}," [p ",[142,881,882],{"class":195},"for",[142,884,885],{"class":199}," p ",[142,887,888],{"class":195},"in",[142,890,891],{"class":199}," psutil.process_iter([",[142,893,894],{"class":151},"\"pid\"",[142,896,897],{"class":199},", ",[142,899,900],{"class":151},"\"name\"",[142,902,897],{"class":199},[142,904,905],{"class":151},"\"create_time\"",[142,907,908],{"class":199},"])\n",[142,910,911,914,917,919,922,925,927,929,932,935,938],{"class":79,"line":234},[142,912,913],{"class":195},"           if",[142,915,916],{"class":199}," p.info[",[142,918,900],{"class":151},[142,920,921],{"class":199},"] ",[142,923,924],{"class":195},"and",[142,926,916],{"class":199},[142,928,900],{"class":151},[142,930,931],{"class":199},"].lower() ",[142,933,934],{"class":195},"==",[142,936,937],{"class":151}," \"excel.exe\"",[142,939,940],{"class":199},"]\n",[142,942,943,945,948,950],{"class":79,"line":246},[142,944,882],{"class":195},[142,946,947],{"class":199}," process ",[142,949,888],{"class":195},[142,951,952],{"class":199}," orphans:\n",[142,954,955,958,961,963,966,968],{"class":79,"line":283},[142,956,957],{"class":242},"    print",[142,959,960],{"class":199},"(process.info[",[142,962,894],{"class":151},[142,964,965],{"class":199},"], process.info[",[142,967,905],{"class":151},[142,969,908],{"class":199},[142,971,972,974,976,979,982,986,989,992,995,998],{"class":79,"line":304},[142,973,321],{"class":242},[142,975,826],{"class":199},[142,977,978],{"class":195},"f",[142,980,981],{"class":151},"\"",[142,983,985],{"class":984},"sSjpA","{",[142,987,988],{"class":242},"len",[142,990,991],{"class":199},"(orphans)",[142,993,994],{"class":984},"}",[142,996,997],{"class":151}," Excel process(es) running\"",[142,999,231],{"class":199},[10,1001,1002],{},"A process created hours ago on a machine where nobody has Excel open is an orphan. One created\nthirty seconds ago probably belongs to the job that is running right now, which is exactly why a\nblanket kill is the wrong tool.",[128,1004,1006],{"id":1005},"killing-only-what-you-started","Killing only what you started",[10,1008,1009],{},"When cleanup has to be guaranteed — an unattended machine, a job that must not leave locks — record\nthe process ID of the instance you started and target only that one.",[133,1011,1013],{"className":186,"code":1012,"language":188,"meta":138,"style":138},"import win32process\nimport win32com.client as win32\n\nexcel = win32.DispatchEx(\"Excel.Application\")\n_, own_pid = win32process.GetWindowThreadProcessId(excel.Hwnd)\ntry:\n    ...\nfinally:\n    try:\n        excel.Quit()\n    finally:\n        import psutil\n        if psutil.pid_exists(own_pid):\n            psutil.Process(own_pid).terminate()      # only ours\n",[14,1014,1015,1022,1032,1036,1048,1058,1065,1070,1076,1082,1086,1092,1099,1106],{"__ignoreMap":138},[142,1016,1017,1019],{"class":79,"line":144},[142,1018,196],{"class":195},[142,1020,1021],{"class":199}," win32process\n",[142,1023,1024,1026,1028,1030],{"class":79,"line":209},[142,1025,196],{"class":195},[142,1027,200],{"class":199},[142,1029,203],{"class":195},[142,1031,206],{"class":199},[142,1033,1034],{"class":79,"line":216},[142,1035,213],{"emptyLinePlaceholder":212},[142,1037,1038,1040,1042,1044,1046],{"class":79,"line":234},[142,1039,219],{"class":199},[142,1041,222],{"class":195},[142,1043,225],{"class":199},[142,1045,228],{"class":151},[142,1047,231],{"class":199},[142,1049,1050,1053,1055],{"class":79,"line":246},[142,1051,1052],{"class":199},"_, own_pid ",[142,1054,222],{"class":195},[142,1056,1057],{"class":199}," win32process.GetWindowThreadProcessId(excel.Hwnd)\n",[142,1059,1060,1063],{"class":79,"line":283},[142,1061,1062],{"class":195},"try",[142,1064,436],{"class":199},[142,1066,1067],{"class":79,"line":304},[142,1068,1069],{"class":242},"    ...\n",[142,1071,1072,1074],{"class":79,"line":309},[142,1073,548],{"class":195},[142,1075,436],{"class":199},[142,1077,1078,1080],{"class":79,"line":318},[142,1079,433],{"class":195},[142,1081,436],{"class":199},[142,1083,1084],{"class":79,"line":430},[142,1085,515],{"class":199},[142,1087,1088,1090],{"class":79,"line":439},[142,1089,472],{"class":195},[142,1091,436],{"class":199},[142,1093,1094,1097],{"class":79,"line":460},[142,1095,1096],{"class":195},"        import",[142,1098,158],{"class":199},[142,1100,1101,1103],{"class":79,"line":469},[142,1102,480],{"class":195},[142,1104,1105],{"class":199}," psutil.pid_exists(own_pid):\n",[142,1107,1108,1111],{"class":79,"line":477},[142,1109,1110],{"class":199},"            psutil.Process(own_pid).terminate()      ",[142,1112,1113],{"class":300},"# only ours\n",[10,1115,1116,1119,1120,1123,1124,1127],{},[14,1117,1118],{},"excel.Hwnd"," gives the application window handle even when it is hidden, and\n",[14,1121,1122],{},"GetWindowThreadProcessId"," turns it into a process ID. Terminating that one process cannot disturb a\ncolleague's spreadsheet, which a ",[14,1125,1126],{},"taskkill \u002Fim excel.exe"," most certainly can.",[128,1129,1131],{"id":1130},"what-an-orphan-actually-costs","What an orphan actually costs",[10,1133,1134],{},"It is tempting to treat a stray process as untidiness rather than a defect, so it is worth being\nprecise about what it breaks. An orphaned instance holds an exclusive lock on every workbook it had\nopen, which means the next run of the same job cannot write its output and fails with a permission\nerror naming a file that looks perfectly free in Explorer. It also holds several hundred megabytes;\nafter a dozen hourly runs, a modest server is swapping.",[10,1136,1137,1138,1141,1142,1144],{},"The subtler cost is that a later ",[14,1139,1140],{},"Dispatch"," call may attach to the orphan rather than starting a\nfresh instance. The script then inherits whatever state the previous run left behind — calculation\nset to manual, alerts suppressed, a half-open workbook, an add-in that failed to load — and produces\noutput that is wrong in ways that do not reproduce anywhere else. That is the failure that takes\ndays to diagnose, and it is entirely prevented by ",[14,1143,544],{}," plus a reliable quit.",[10,1146,1147],{},"Because the symptoms surface elsewhere, orphan cleanup is worth treating as a property of the job\nrather than a tidy-up at the end. A run that cannot guarantee it released Excel should be treated as\na failed run even if it produced a file, because the machine is now in a state the next run does not\nexpect.",[128,1149,1151],{"id":1150},"verifying-cleanup-as-part-of-the-job","Verifying cleanup as part of the job",[10,1153,1154],{},"The cheapest check is to count Excel processes before and after, and to log the difference. On a\ndedicated automation machine the count should return to whatever it started at; anything else is a\nleak worth investigating while the change that caused it is still fresh.",[133,1156,1158],{"className":186,"code":1157,"language":188,"meta":138,"style":138},"import logging\nimport psutil\n\ndef excel_count() -> int:\n    return sum(1 for p in psutil.process_iter([\"name\"])\n               if (p.info[\"name\"] or \"\").lower() == \"excel.exe\")\n\nbefore = excel_count()\ntry:\n    run_report()\nfinally:\n    after = excel_count()\n    if after > before:\n        logging.warning(\"leaked %d Excel process(es): %d -> %d\", after - before, before, after)\n",[14,1159,1160,1167,1173,1177,1192,1217,1244,1248,1258,1264,1269,1275,1284,1298],{"__ignoreMap":138},[142,1161,1162,1164],{"class":79,"line":144},[142,1163,196],{"class":195},[142,1165,1166],{"class":199}," logging\n",[142,1168,1169,1171],{"class":79,"line":209},[142,1170,196],{"class":195},[142,1172,158],{"class":199},[142,1174,1175],{"class":79,"line":216},[142,1176,213],{"emptyLinePlaceholder":212},[142,1178,1179,1181,1184,1187,1190],{"class":79,"line":234},[142,1180,374],{"class":195},[142,1182,1183],{"class":377}," excel_count",[142,1185,1186],{"class":199},"() -> ",[142,1188,1189],{"class":242},"int",[142,1191,436],{"class":199},[142,1193,1194,1197,1200,1202,1204,1207,1209,1211,1213,1215],{"class":79,"line":246},[142,1195,1196],{"class":195},"    return",[142,1198,1199],{"class":242}," sum",[142,1201,826],{"class":199},[142,1203,599],{"class":242},[142,1205,1206],{"class":195}," for",[142,1208,885],{"class":199},[142,1210,888],{"class":195},[142,1212,891],{"class":199},[142,1214,900],{"class":151},[142,1216,908],{"class":199},[142,1218,1219,1222,1225,1227,1229,1232,1235,1238,1240,1242],{"class":79,"line":283},[142,1220,1221],{"class":195},"               if",[142,1223,1224],{"class":199}," (p.info[",[142,1226,900],{"class":151},[142,1228,921],{"class":199},[142,1230,1231],{"class":195},"or",[142,1233,1234],{"class":151}," \"\"",[142,1236,1237],{"class":199},").lower() ",[142,1239,934],{"class":195},[142,1241,937],{"class":151},[142,1243,231],{"class":199},[142,1245,1246],{"class":79,"line":304},[142,1247,213],{"emptyLinePlaceholder":212},[142,1249,1250,1253,1255],{"class":79,"line":309},[142,1251,1252],{"class":199},"before ",[142,1254,222],{"class":195},[142,1256,1257],{"class":199}," excel_count()\n",[142,1259,1260,1262],{"class":79,"line":318},[142,1261,1062],{"class":195},[142,1263,436],{"class":199},[142,1265,1266],{"class":79,"line":430},[142,1267,1268],{"class":199},"    run_report()\n",[142,1270,1271,1273],{"class":79,"line":439},[142,1272,548],{"class":195},[142,1274,436],{"class":199},[142,1276,1277,1280,1282],{"class":79,"line":460},[142,1278,1279],{"class":199},"    after ",[142,1281,222],{"class":195},[142,1283,1257],{"class":199},[142,1285,1286,1289,1292,1295],{"class":79,"line":469},[142,1287,1288],{"class":195},"    if",[142,1290,1291],{"class":199}," after ",[142,1293,1294],{"class":195},">",[142,1296,1297],{"class":199}," before:\n",[142,1299,1300,1303,1306,1309,1312,1314,1317,1319,1321,1324,1327],{"class":79,"line":477},[142,1301,1302],{"class":199},"        logging.warning(",[142,1304,1305],{"class":151},"\"leaked ",[142,1307,1308],{"class":984},"%d",[142,1310,1311],{"class":151}," Excel process(es): ",[142,1313,1308],{"class":984},[142,1315,1316],{"class":151}," -> ",[142,1318,1308],{"class":984},[142,1320,981],{"class":151},[142,1322,1323],{"class":199},", after ",[142,1325,1326],{"class":195},"-",[142,1328,1329],{"class":199}," before, before, after)\n",[10,1331,1332,1333,1337],{},"Three lines of instrumentation turn an invisible defect into a warning in the log the morning after\nit is introduced. Pairing that with the run-level logging in\n",[19,1334,1336],{"href":1335},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Flog-python-excel-script-output-to-a-file\u002F","Log Python Excel Script Output to a File","\ngives a record that answers the question a week later, when the permission error finally appears and\nsomebody asks when it started.",[128,1339,1341],{"id":1340},"common-pitfalls","Common pitfalls",[1343,1344,1345,1361],"table",{},[1346,1347,1348],"thead",{},[1349,1350,1351,1355,1358],"tr",{},[1352,1353,1354],"th",{},"Symptom",[1352,1356,1357],{},"Cause",[1352,1359,1360],{},"Fix",[1362,1363,1364,1385,1399,1415,1431,1445],"tbody",{},[1349,1365,1366,1373,1379],{},[1367,1368,1369,1370,1372],"td",{},"Hidden ",[14,1371,16],{}," processes accumulate",[1367,1374,1375,1376,1378],{},"References still held when ",[14,1377,174],{}," ran",[1367,1380,1381,1382,1384],{},"Close the workbook, ",[14,1383,556],{}," the objects, then quit",[1349,1386,1387,1393,1396],{},[1367,1388,1389,1392],{},[14,1390,1391],{},"PermissionError"," opening a file the script wrote",[1367,1394,1395],{},"An orphan still holds the lock",[1367,1397,1398],{},"Clean up the orphans; see the detection snippet above",[1349,1400,1401,1406,1409],{},[1367,1402,1403],{},[14,1404,1405],{},"CoInitialize has not been called",[1367,1407,1408],{},"COM used on a thread that never initialised it",[1367,1410,1411,1414],{},[14,1412,1413],{},"pythoncom.CoInitialize()"," at the top of the thread",[1349,1416,1417,1420,1425],{},[1367,1418,1419],{},"Quitting closes a colleague's workbook",[1367,1421,1422,1424],{},[14,1423,1140],{}," attached to their running instance",[1367,1426,1427,1428,1430],{},"Use ",[14,1429,544],{}," for a private instance",[1349,1432,1433,1436,1442],{},[1367,1434,1435],{},"Cleanup skipped after an exception",[1367,1437,1438,1439,1441],{},"No ",[14,1440,548],{}," block",[1367,1443,1444],{},"Wrap the session in a helper like the one above",[1349,1446,1447,1450,1453],{},[1367,1448,1449],{},"Excel reappears on the next run with a recovery prompt",[1367,1451,1452],{},"The previous process was killed with a file open",[1367,1454,1455,1456,1459],{},"Close workbooks with ",[14,1457,1458],{},"SaveChanges=False"," before terminating",[128,1461,1463],{"id":1462},"performance-and-scale","Performance and scale",[10,1465,1466,1467,1469],{},"Starting Excel is expensive — typically one to three seconds, more on a cold machine — so a job that\nprocesses twenty workbooks should start one instance and open twenty files, not start twenty\ninstances. The lifecycle rules do not change; the ",[14,1468,548],{}," simply moves outward.",[133,1471,1473],{"className":186,"code":1472,"language":188,"meta":138,"style":138},"import win32com.client as win32\n\nexcel = win32.DispatchEx(\"Excel.Application\")\nexcel.Visible = False\nexcel.DisplayAlerts = False\ntry:\n    for path in paths:\n        book = excel.Workbooks.Open(path, UpdateLinks=0)\n        try:\n            book.RefreshAll()\n            book.Save()\n        finally:\n            book.Close(SaveChanges=False)\nfinally:\n    excel.Quit()\n",[14,1474,1475,1485,1489,1501,1509,1518,1524,1537,1553,1559,1563,1568,1574,1586,1592],{"__ignoreMap":138},[142,1476,1477,1479,1481,1483],{"class":79,"line":144},[142,1478,196],{"class":195},[142,1480,200],{"class":199},[142,1482,203],{"class":195},[142,1484,206],{"class":199},[142,1486,1487],{"class":79,"line":209},[142,1488,213],{"emptyLinePlaceholder":212},[142,1490,1491,1493,1495,1497,1499],{"class":79,"line":216},[142,1492,219],{"class":199},[142,1494,222],{"class":195},[142,1496,225],{"class":199},[142,1498,228],{"class":151},[142,1500,231],{"class":199},[142,1502,1503,1505,1507],{"class":79,"line":234},[142,1504,237],{"class":199},[142,1506,222],{"class":195},[142,1508,243],{"class":242},[142,1510,1511,1514,1516],{"class":79,"line":246},[142,1512,1513],{"class":199},"excel.DisplayAlerts ",[142,1515,222],{"class":195},[142,1517,243],{"class":242},[142,1519,1520,1522],{"class":79,"line":283},[142,1521,1062],{"class":195},[142,1523,436],{"class":199},[142,1525,1526,1529,1532,1534],{"class":79,"line":304},[142,1527,1528],{"class":195},"    for",[142,1530,1531],{"class":199}," path ",[142,1533,888],{"class":195},[142,1535,1536],{"class":199}," paths:\n",[142,1538,1539,1541,1543,1545,1547,1549,1551],{"class":79,"line":309},[142,1540,442],{"class":199},[142,1542,222],{"class":195},[142,1544,447],{"class":199},[142,1546,451],{"class":450},[142,1548,222],{"class":195},[142,1550,46],{"class":242},[142,1552,231],{"class":199},[142,1554,1555,1557],{"class":79,"line":318},[142,1556,749],{"class":195},[142,1558,436],{"class":199},[142,1560,1561],{"class":79,"line":430},[142,1562,766],{"class":199},[142,1564,1565],{"class":79,"line":439},[142,1566,1567],{"class":199},"            book.Save()\n",[142,1569,1570,1572],{"class":79,"line":460},[142,1571,784],{"class":195},[142,1573,436],{"class":199},[142,1575,1576,1578,1580,1582,1584],{"class":79,"line":469},[142,1577,500],{"class":199},[142,1579,503],{"class":450},[142,1581,222],{"class":195},[142,1583,182],{"class":242},[142,1585,231],{"class":199},[142,1587,1588,1590],{"class":79,"line":477},[142,1589,548],{"class":195},[142,1591,436],{"class":199},[142,1593,1594],{"class":79,"line":497},[142,1595,1596],{"class":199},"    excel.Quit()\n",[25,1598,34,1603,34,1606,34,1609,34,1612,34,1618,34,1624,34,1630,34,1635,34,1639,34,1643,34,1646,34,1651,34,1655,34,1658,34,1661,34,1664,34,1672,34,1677,34,1682,34,1685,34,1689,34,1692],{"viewBox":1599,"role":28,"ariaLabelledBy":1600,"xmlns":32,"style":33},"0 0 760 201",[1601,1602],"orp-batch-t","orp-batch-d",[36,1604,1605],{"id":1601},"One instance for many files, not one each",[40,1607,1608],{"id":1602},"Starting Excel costs one to three seconds, so opening twenty workbooks in a single instance is far cheaper than starting twenty instances, while the cleanup rules stay the same.",[44,1610],{"x":46,"y":46,"width":47,"height":1611,"fill":49},"201",[44,1613],{"x":1614,"y":584,"width":1615,"height":1616,"rx":1617,"fill":93,"stroke":94,"style":66},"20","270.0","139","14",[51,1619,1623],{"x":1620,"y":1621,"style":1622},"155.0","54","font-size:13px;font-weight:700;fill:var(--accent,#d81b73);text-anchor:middle","an instance per file",[79,1625],{"x1":1626,"y1":1627,"x2":1628,"y2":1627,"stroke":94,"style":1629},"36","64","274.0","stroke-width:1px",[51,1631,1634],{"x":1620,"y":1632,"style":1633},"86","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","20 startups",[51,1636,1638],{"x":1620,"y":1637,"style":1633},"109","40 seconds of overhead",[51,1640,1642],{"x":1620,"y":1641,"style":1633},"132","20 chances to orphan",[44,1644],{"x":1645,"y":584,"width":1615,"height":1616,"rx":1617,"fill":588,"stroke":589,"style":66},"470.0",[51,1647,1650],{"x":1648,"y":1621,"style":1649},"605.0","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","one instance, 20 files",[79,1652],{"x1":1653,"y1":1627,"x2":1654,"y2":1627,"stroke":589,"style":1629},"486.0","724.0",[51,1656,1657],{"x":1648,"y":1632,"style":1633},"1 startup",[51,1659,1660],{"x":1648,"y":1637,"style":1633},"seconds of overhead",[51,1662,1663],{"x":1648,"y":1641,"style":1633},"one finally block",[44,1665],{"x":1666,"y":1667,"width":1668,"height":1669,"rx":1670,"fill":1671,"stroke":84},"316.0","78.5","128","38","19","#ebebfd",[51,1673,1676],{"x":53,"y":1674,"style":1675},"102.5","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","reuse",[79,1678],{"x1":1679,"y1":1680,"x2":1681,"y2":1680,"stroke":84,"style":66},"295.0","97.5","309.0",[86,1683],{"points":1684,"fill":89},"309.0,97.5 300.0,92.5 300.0,102.5",[79,1686],{"x1":1687,"y1":1680,"x2":1688,"y2":1680,"stroke":84,"style":66},"449.0","463.0",[86,1690],{"points":1691,"fill":89},"463.0,97.5 454.0,92.5 454.0,102.5",[51,1693,1695],{"x":53,"y":1694,"style":125},"187","move the finally outward, not the logic",[10,1697,1698],{},"The one thing to watch in a long-running loop is that Excel's memory does not return fully between\nworkbooks. On a batch of several hundred files, restarting the application every fifty or so keeps\nthe footprint flat — inelegant, but considerably cheaper than the alternative of discovering the\nlimit at three in the morning.",[128,1700,1702],{"id":1701},"conclusion","Conclusion",[10,1704,1705,1706,1708,1709,1711,1712,1714],{},"Treat an Excel instance the way you would treat a file handle or a database connection: acquire it\nexplicitly, release it in a ",[14,1707,548],{},", and never assume the garbage collector will do it for you.\nUse ",[14,1710,544],{}," so the instance is yours to quit, close workbooks before quitting, drop the Python\nreferences, and call ",[14,1713,674],{}," in any thread that is not the one the interpreter started on.\nFor unattended machines, record the process ID at the start so cleanup can be guaranteed without\ntouching anybody else's work.",[128,1716,1718],{"id":1717},"frequently-asked-questions","Frequently asked questions",[10,1720,1721,1725],{},[1722,1723,1724],"strong",{},"Why does Excel keep running after Quit()?","\nBecause Quit only closes the application when nothing else holds a reference to it. A worksheet or workbook object still alive in Python keeps the process open, and in an interactive session the traceback of a caught exception holds references too.",[10,1727,1728,1731],{},[1722,1729,1730],{},"Is killing EXCEL.EXE a reasonable fallback?","\nAs a last resort in a dedicated automation account, yes — but never blindly, because it will also close a workbook a person had open. Kill only the process ID you started, which you can obtain from the application's Hwnd before you begin.",[10,1733,1734,1737],{},[1722,1735,1736],{},"Does the with statement help?","\npywin32 objects are not context managers, so no — you have to write the try\u002Ffinally yourself, or wrap it once in a helper. xlwings does provide App as a context manager, which is one of the ergonomic reasons to prefer it.",[10,1739,1740,1743],{},[1722,1741,1742],{},"What is CoInitialize for?","\nCOM has to be initialised per thread. The main thread of a simple script usually gets that for free through pywin32, but a worker thread, a Flask request handler or a scheduler job does not — and the resulting error mentions CoInitialize rather than threads.",[128,1745,1747],{"id":1746},"related","Related",[1749,1750,1751,1758,1765,1772,1779],"ul",{},[1752,1753,1754,1755,1757],"li",{},"Up one level: ",[19,1756,22],{"href":21}," — the session skeleton these rules protect.",[1752,1759,1760,1764],{},[19,1761,1763],{"href":1762},"\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"," — the hangs that leave orphans behind in the first place.",[1752,1766,1767,1771],{},[19,1768,1770],{"href":1769},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fhandle-permission-denied-when-writing-excel-in-python\u002F","Handle \"Permission Denied\" When Writing Excel in Python"," — the error an orphaned process causes downstream.",[1752,1773,1774,1778],{},[19,1775,1777],{"href":1776},"\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 that provides a context manager for this.",[1752,1780,1781,1785],{},[19,1782,1784],{"href":1783},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fretry-a-failed-excel-report-job-in-python\u002F","Retry a Failed Excel Report Job in Python"," — making the surrounding job resilient once cleanup is reliable.",[1787,1788,1789],"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 .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 .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}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":138,"searchDepth":209,"depth":209,"links":1791},[1792,1793,1794,1795,1796,1797,1798,1799,1800,1801,1802,1803,1804],{"id":130,"depth":209,"text":131},{"id":167,"depth":209,"text":168},{"id":342,"depth":209,"text":343},{"id":564,"depth":209,"text":565},{"id":850,"depth":209,"text":851},{"id":1005,"depth":209,"text":1006},{"id":1130,"depth":209,"text":1131},{"id":1150,"depth":209,"text":1151},{"id":1340,"depth":209,"text":1341},{"id":1462,"depth":209,"text":1463},{"id":1701,"depth":209,"text":1702},{"id":1717,"depth":209,"text":1718},{"id":1746,"depth":209,"text":1747},"2026-09-04","Excel exits only when every reference is released. Learn the try\u002Ffinally shape, CoInitialize on worker threads, and how to detect and kill just the instance you started.","md",[1809,1811,1813,1815],{"q":1724,"a":1810},"Because Quit only closes the application when nothing else holds a reference to it. A worksheet or workbook object still alive in Python keeps the process open, and in an interactive session the traceback of a caught exception holds references too.",{"q":1730,"a":1812},"As a last resort in a dedicated automation account, yes — but never blindly, because it will also close a workbook a person had open. Kill only the process ID you started, which you can obtain from the application's Hwnd before you begin.",{"q":1736,"a":1814},"pywin32 objects are not context managers, so no — you have to write the try\u002Ffinally yourself, or wrap it once in a helper. xlwings does provide App as a context manager, which is one of the ergonomic reasons to prefer it.",{"q":1742,"a":1816},"COM has to be initialised per thread. The main thread of a simple script usually gets that for free through pywin32, but a worker thread, a Flask request handler or a scheduler job does not — and the resulting error mentions CoInitialize rather than threads.",{"breadcrumb":1818},[1819,1822,1825],{"name":1820,"item":1821},"Home","\u002F",{"name":1823,"item":1824},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":22,"item":21},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fclose-excel-cleanly-and-avoid-orphan-com-processes",{"title":5,"description":1828},"Stop hidden EXCEL.EXE processes accumulating: release references in order, quit in a finally, initialise COM per thread, and terminate only the PID your script started.","close-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","how-to","d7OpFYnE2QUAEF2tYeXOYCLfoJd0sckG0Zw2_UU1MJg",[1834,1837],{"title":22,"path":1835,"stem":1836,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Findex",{"title":1763,"path":1838,"stem":1839,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fhandle-com-errors-and-excel-dialog-prompts-in-python","getting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Fhandle-com-errors-and-excel-dialog-prompts-in-python\u002Findex",1788710154301]