Python 清晰地将单个语句包装在 try except 块中

10 投票
4 回答
5575 浏览
提问于 2025-04-17 01:05

我现在正在用Python自动化处理Excel,使用的是com接口。这个功能完全正常,能做到我想要的效果,但我发现了一些意想不到的事情。有时候,我用的某些Excel命令会莫名其妙地失败,抛出异常;而有时候,它们又能正常工作。

在我用VB写的相应代码中,这个问题似乎是正常现象,通常会用一个On Error Resume Next语句来处理。显然,Python没有这个语句。

我不能把所有的代码都放在一个try except循环里,因为它可能在执行过程中“失败”,导致没有正确完成。所以,有没有什么更Pythonic的方式,把几个独立的语句放进一个try except块里?我希望能比下面的代码更简洁:

try:
   statement
except:
   pass
try:
   statement
except:
   pass

相关的代码是excel.Selection.Borders那部分。

def addGridlines(self, infile, outfile):
    """convert csv to excel, and add gridlines"""
    # set constants for excel
    xlDiagonalDown = 5
    xlDiagonalUp = 6
    xlNone = -4142
    xlContinuous = 1
    xlThin = 2
    xlAutomatic = -4105
    xlEdgeLeft = 7
    xlEdgeTop = 8
    xlEdgeBottom = 9
    xlEdgeRight = 10
    xlInsideVertical = 11
    xlInsideHorizontal = 12
            # open file
    excel = win32com.client.Dispatch('Excel.Application')
    workbook = excel.Workbooks.Open(infile)
    worksheet = workbook.Worksheets(1)

    # select all cells
    worksheet.Range("A1").CurrentRegion.Select()
    # add gridlines, sometimes some of these fail, so we have to wrap each in a try catch block
    excel.Selection.Borders(xlDiagonalDown).LineStyle = xlNone
    excel.Selection.Borders(xlDiagonalUp).LineStyle = xlNone
    excel.Selection.Borders(xlDiagonalUp).LineStyle = xlNone
    excel.Selection.Borders(xlEdgeLeft).LineStyle = xlContinuous
    excel.Selection.Borders(xlEdgeLeft).Weight = xlThin
    excel.Selection.Borders(xlEdgeLeft).ColorIndex = xlAutomatic
    excel.Selection.Borders(xlEdgeTop).LineStyle = xlContinuous
    excel.Selection.Borders(xlEdgeTop).Weight = xlThin
    excel.Selection.Borders(xlEdgeTop).ColorIndex = xlAutomatic
    excel.Selection.Borders(xlEdgeBottom).LineStyle = xlContinuous
    excel.Selection.Borders(xlEdgeBottom).Weight = xlThin
    excel.Selection.Borders(xlEdgeBottom).ColorIndex = xlAutomatic
    excel.Selection.Borders(xlEdgeRight).LineStyle = xlContinuous
    excel.Selection.Borders(xlEdgeRight).Weight = xlThin
    excel.Selection.Borders(xlEdgeRight).ColorIndex = xlAutomatic
    excel.Selection.Borders(xlInsideVertical).LineStyle = xlContinuous
    excel.Selection.Borders(xlInsideVertical).Weight = xlThin
    excel.Selection.Borders(xlInsideVertical).ColorIndex = xlAutomatic
    excel.Selection.Borders(xlInsideHorizontal).LineStyle = xlContinuous
    excel.Selection.Borders(xlInsideHorizontal).Weight = xlThin
    excel.Selection.Borders(xlInsideHorizontal).ColorIndex = xlAutomatic
    # refit data into columns
    excel.Cells.Select()
    excel.Cells.EntireColumn.AutoFit()
    # save new file in excel format
    workbook.SaveAs(outfile, FileFormat=1)
    workbook.Close(False)
    excel.Quit()
    del excel

更新

也许需要对错误的部分做点解释。在我的测试机器上,两次完全相同的运行,代码和文件都一样,结果却不同。一轮运行对每个xlInsideVertical行抛出异常,另一轮则对每个xlInsideHorizontal行抛出异常。最后,第三轮运行完全没有抛出异常。

据我所知,Excel认为这是正常行为,因为我是在克隆Excel宏生成器生成的VB代码,而不是人为编写的VB代码。当然,这可能是个错误的假设。

如果每一行都放在一个try except块里是可以正常工作的,但我只是想要更简短、更明显的方式,因为20行代码各自放在自己的try catch循环里,后面会很麻烦。

更新2

这是一个经过处理的CSV文件,用于测试:gist文件

结论

Vsekhar提供的答案非常完美。它把异常处理抽象化,这样以后如果有时间,我可以真正处理发生的异常。它还允许记录异常,这样它们不会消失,不会阻止其他异常,而且足够小,六个月后也能轻松管理。

4 个回答

4

这段话的意思是,这个代码主要是用来包装函数调用的,但你可以把它扩展一下,让它也能处理属性的访问。也就是说,它可以帮助你处理那些嵌套的属性访问,最后只需要把__setattr__放在你的try:except块里就可以了。

在你的情况下,可能只需要捕获一些特定类型的错误,这样做会更合理(正如@vsekhar所说)。

def onErrorResumeNext(wrapped):
    class Proxy(object):
        def __init__(self, fn):
            self.__fn = fn

        def __call__(self, *args, **kwargs):
            try:
                return self.__fn(*args, **kwargs)
            except:
                print "swallowed exception"

    class VBWrapper(object):
        def __init__(self, wrapped):
            self.wrapped = wrapped

        def __getattr__(self, name):
            return Proxy(eval('self.wrapped.'+name))

    return VBWrapper(wrapped)

举个例子:

exceptionProofBorders = onErrorResumeNext(excel.Selection.Borders)
exceptionProofBorders(xlDiagonalDown).LineStyle = xlNone
exceptionProofBorders(xlDiagonalup).LineStyle = xlNone
10

异常的发生绝对不是“没有明显原因”的。总是有原因存在,而这个原因需要被解决。否则,你的程序就会开始产生“随机”的数据,而这种“随机”是由你隐藏的错误所决定的。

当然,你需要一个解决方案来处理你的问题。以下是我的建议:

  1. 创建一个包装类,里面实现你需要的所有方法,并把这些方法委托给真实的Excel实例。

  2. 在每个方法前加一个装饰器,把这个方法放在一个try except块中,并记录异常。绝对不要忽略异常

这样代码就能正常工作,客户可以使用,这样你就有时间去找出问题的原因。我的猜测是:a) Excel没有提供有用的错误信息,或者 b) 包装代码忽略了真实的异常,让你一头雾水,或者 c) Excel方法返回了一个错误代码(比如“false”表示“失败”),你需要调用另一个Excel方法来确定问题的原因。

[编辑] 根据下面的评论,归结为“我老板不在乎,我无能为力”:你错过了一个关键点:做决定是你老板的责任,但提供选项和优缺点的清单是你的责任,这样她才能做出明智的决定。光坐在那里说“我无能为力”只会让你陷入你想要避免的麻烦。

举个例子:

解决方案1:忽略错误

优点:工作量最少

缺点:结果数据可能是错误的或随机的。如果重要的商业决策基于这些数据,风险就很高,可能会做出错误的决定。

解决方案2:记录错误

优点:工作量小,用户可以快速开始使用结果,给你时间去找出问题的根源

缺点:“如果今天不能解决,明天你怎么会有时间去解决?”而且,作为非专家,你可能需要很长时间才能找到问题的根源。

解决方案3:请教专家

找一个领域内的专家,帮助他/她查看或改进解决方案。

优点:比自己学习COM的细节更快找到解决方案

缺点:费用高,但成功的机会大。还可能发现我们自己都不知道的问题。

...

我想你能看出其中的规律。老板做出错误决定是因为我们(自愿地)让他们这样做。任何老板在做决定时都希望有确凿的事实和意见(当然,那些不这样做的就不应该当老板,所以这是一个明确的信号,告诉你什么时候该开始找新工作)。

如果你选择解决方案#2,那就采用包装的方法。查看文档,了解如何编写装饰器(IBM的示例)。包装所有方法只需几分钟的工作,这样你就有东西可以使用了。

下一步是创建一个较小的示例,偶尔会出错,然后在这里发布关于Python、Excel和COM包装器的具体问题,以找出问题的原因。

[编辑2] 这里有一些代码,将“危险”的部分包装在一个辅助类中,使更新样式更简单:

class BorderHelper(object):
    def __init__(self, excel):
        self.excel = excel

    def set( type, LineStyle = None, Weight = None, Color = None ):
        border = self.excel.Selection.Borders( type )

        try:
            if LineStyle is not None:
                border.LineStyle = LineStyle
        except:
            pass # Ignore if a style can't be set

        try:
            if Weight is not None:
                border.Weight = Weight
        except:
            pass # Ignore if a style can't be set

        try:
            if Color is not None:
                border.Color = Color
        except:
            pass # Ignore if a style can't be set

使用:

    borders = BorderHelper( excel )

    borders.set( xlDiagonalDown, LineStyle = xlNone )
    borders.set( xlDiagonalUp, LineStyle = xlNone )
    borders.set( xlEdgeLeft, LineStyle = xlContinuous, Weight = xlThin, Color = xlAutomatic )
    ...
14

考虑把抑制异常的部分抽象出来。正如Aaron所说,不要随便吞掉异常。

class Suppressor:
    def __init__(self, exception_type):
        self._exception_type = exception_type

    def __call__(self, expression):
        try:
            exec expression
        except self._exception_type as e:
            print 'Suppressor: suppressed exception %s with content \'%s\'' % (type(self._exception_type), e)
            # or log.msg('...')

接下来,在你当前代码的错误追踪信息中,仔细查看到底是什么异常被抛出,然后为这个特定的异常创建一个抑制器:

s = Suppressor(excel.WhateverError) # TODO: put your exception type here
s('excel.Selection.Borders(xlDiagonalDown).LineStyle = xlNone')

这样一来,你的代码会逐行执行(这样错误追踪信息仍然有用),而且你只会抑制那些你明确想要抑制的异常。其他的异常会像往常一样继续传播。

撰写回答