如果脚本失败,如何确保关闭 xlwings 连接

How to ensure xlwings connection is closed if script fails

我正在尝试用 xlwings 开发一些东西,因为我需要用宏等操作 xls 文件。虽然关闭连接总是好的,但 Excel 是臭名昭著的,因为它会阻止访问如果不止一个实例是 运行。因此,即使我的代码在上游某处失败,我也需要确保应用程序关闭。

我目前正在使用跨越整个脚本的 try 语句执行此操作,当它失败时调用 app.quit()。但这会抑制我的错误消息,这使得调试变得困难。所以我觉得一定有更好的。

在另一个上下文中,我看到 with 被使用。而且我觉得它也适用于此,但我不明白它是如何工作的,也不了解它在这种特定情况下的工作方式。

import xlwings as xw

def myexcel():
    try:
        #connect to Excel app in the background
        excel = xw.App(visible=False)
        # open excel book
        wb = excel.books.open(str(file))
        # asign the active app so it can be closed later
        app = xw.apps.active

        # more code goes here 

    except:
        app.quit()

如何确保 excel 连接始终关闭,无论采用何种最有效的方式? 如果 with 是解决方案,我也希望能提供指向良好资源的指针以了解有关该概念的更多信息。

你做对了 - 在这种情况下使用 try 块是正确的方法。 With 语句在您需要打开文件时很好,但不适用于您使用以自己的方式打开 excel 文件的库时的用例。

要显示异常的详细信息,您可以按如下方式更改代码:

import xlwings as xw

def myexcel():
    try:
        #connect to Excel app in the background
        excel = xw.App(visible=False)
        # open excel book
        wb = excel.books.open(str(file))
        # asign the active app so it can be closed later
        app = xw.apps.active

        # more code goes here 

    finally:
        app.quit()
    except Exception as e:
        print('exception catched: {}'.format(e))
        app.quit()

如您所述,您可以使用 with 语句并构建您自己的 contextmanager。这是一个基于您的代码的转换示例:

import xlwings as xw

class MyExcelApp:
    def __init__(self):
        self.excel = xw.App(visible=False)

    def __enter__(self):
        return self.excel

    def __exit__(self, exc, value, traceback):
        # Handle your app-specific exceptions (exc) here
        self.excel.quit()
        return True   
        # ^ return True only if you intend to catch all errors in here.
        # Otherwise, leave as is and use try... except on the outside.

class MyExcelWorkbook:
    def __init__(self, xlapp, bookname):
        self.workbook = xlapp.books.open(bookname)

    def __enter__(self):
        return self.workbook

    def __exit__(self, exc, value, traceback):
        # Handle your workbook specific exceptions (exc) here
        # self.workbook.save()   # depends what you want to do here
        self.workbook.close()
        return True   
        # ^ return True only if you intend to catch all errors in here.
        # Otherwise, leave as is and use try... except on the outside.    

有了这个设置,您可以像这样简单地调用它:

with MyExcelApp() as app:
    with MyExcelWorkbook(filename) as wb:
        # do something with wb

您也可以实施它 with a generator,这将与其他答案非常相似。 这是一个简化版本:

import xlwings as xw
from contextlib import contextmanager

@contextmanager
def my_excel_app():
    app = xw.App(visible=False)
    try:
        yield app

    except:  # <-- Add SPECIFIC app exceptions
        # Handle the errors

    finally:
        app.quit()

用法:

with my_excel() as app:
    wb = app.books.open(some_file)
    # do something...

首选方案
xlwings 在 v0.24.3 中针对这个问题添加了解决方案: xlwings.App() 现在可以用作上下文管理器,确保在 Windows 上没有剩余的僵尸进程,即使您使用隐藏实例并且代码失败也是如此。因此建议尽可能使用它,例如:

import xlwings as xw
with xw.App(visible=False) as app:
    wb = xw.Book("test.xlsx")
    sheet = wb.sheets['sheet1']
    # To evoke an error, I try to call an non-exisiting sheet here.
    nonexistent_sheet["A1"]

v24.0.3之前的解决方法
您可以使用库 traceback,这使得调试更容易,因为错误显示为红色。看这个例子:

import xlwings as xw
import traceback

filename = "test.xlsx"

try:
    # Do what you want here in the try block. For example, the following lines.
    app = xw.App(visible=False)
    wb = xw.Book(filename)
    sheet = wb.sheets['sheet1']
    # To evoke an error, I try to call an nonexistent sheet here.
    nonexistent_sheet["A1"]

# Use BaseException because it catches all possible exceptions: 
except BaseException:
    # This prints the actual error in a verbose way.
    print(traceback.print_exc())
    app.quit()

错误显示print(traceback.print_exc())如下: