【问题标题】:Python clean way to wrap individual statements in a try except blockPython 干净的方式将单个语句包装在 try except 块中
【发布时间】:2011-11-08 10:22:37
【问题描述】:

我目前正在使用 com 对 Excel 进行一些 Python 自动化。它功能齐全,可以做我想做的事,但我发现了一些令人惊讶的事情。有时,我使用的某些 Excel 命令会因为没有明显原因的异常而失败。其他时候,他们会工作。

在我正在做的 VB 等效代码中,这个问题显然被认为是正常的,并且被 On Error Resume Next 声明所覆盖。 Python当然没有这种说法。

我不能在try except 循环中结束整个集合,因为它可能在中途“失败”并且无法正确完成。那么,将几个独立语句包装到 try except 块中的pythonic方法是什么?具体来说,比以下更干净的东西:

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 块中的每一行起作用

更新2

这是一个用于测试的已清理 CSV 文件:gist file

结论

Vsekhar 提供的答案是完美的。它抽象出异常抑制,以便以后,如果有时间,我可以在异常发生时实际处理它们。它还允许记录异常,以便它们不会消失,不会停止其他异常,并且足够小,可以在六个月后轻松管理。

【问题讨论】:

  • 一开始就让它不失败怎么样? Python 不是 PHP 或 Visual Basic,不幸的是它强制执行合理的错误处理:(
  • 您能否将所有调用保存在一个列表中,然后在循环中使用单个 try 语句对其进行迭代?只是不知道如何“保存”这种调用。
  • @rpInt:语句不是 Python 中的一流对象——它们不能分配给变量并传递给函数
  • 你能上传一个显示问题的虚拟表吗?您的代码似乎对我来说始终如一(在删除 xlDiagonalup 之后——注意小写的 u 行,这让我认为上面的代码不可能是您实际运行的代码)。
  • @Spencer Rathbun:感谢您发布示例文件。我在 07 年尝试了数百次,没有一次失败,所以我在 Excel 方面没有任何有用的建议,因为我无法让它崩溃。无用的事情涉及尝试选择哪个区域,看看(例如)它是否总是适用于小的 2x2 块,或者进行手动选择而不是接受区域等,但这些都是基于尝试想象 Excel 问题可能是什么的随机猜测。

标签: python excel vba com try-catch


【解决方案1】:

考虑抽象出抑制。就 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')

通过这种方式,您可以获得逐行执行(因此您的回溯仍然会有所帮助),并且您只会抑制您明确预期的异常。其他异常照常传播。

【讨论】:

  • 这非常符合 Aaron Digulla 所建议的精神,只是您正在默默地“吞下”指定的异常。默认情况下避免抑制异常确实是正确的,但是IMO最好至少记录你确实抑制的异常,或者将它们存储在一个数据结构中可以在程序执行结束时转储,以便您查看实际发生的情况。
  • @Peter:很公平,我添加了日志记录行为
【解决方案2】:

永远不会“无缘无故”地发生异常。总有一个原因,这个原因需要解决。否则,您的程序将开始生成“随机”数据,其中“随机”受您隐藏的错误的支配。

当然,您需要解决问题的方法。这是我的建议:

  1. 创建一个包装类来实现您需要的所有方法并将它们委托给真正的 Excel 实例。

  2. 在每个方法之前添加一个装饰器,将方法包装在 try except 块中并记录异常。 永远不要吞下异常

现在代码适用于您的客户,这让您有时间找出问题的原因。我的猜测是 a) Excel 不会产生有用的错误消息,或者 b) 包装器代码吞下真正的异常,让你一无所知,或者 c) Excel 方法返回错误代码(如“失败”的“假”)并且您需要调用另一个 Excel 方法来确定问题的原因。

[编辑]根据下面的评论归结为“我的老板不在乎,我无能为力”:你错过了一个关键点:这是你的老板 有责任做出决定,但你的有责任向她提供选项列表以及优点/缺点,以便她做出正确的决定。只是坐在那里说“我无能为力”会让你陷入你试图避免的麻烦。

例子:

解决方案 1:忽略错误

专业版:工作量最少 缺点:结果数据有可能是错误的或随机的。如果重要的业务决策基于它,那么这些决策很可能是错误的。

解决方案 2:记录错误

优点:工作量小,用户可以快速开始使用结果,争取时间找出问题的根源 骗子:“如果你今天不能修好,是什么让你觉得你明天有时间修呢?”此外,您可能需要很长时间才能找到问题的根源,因为您不是专家

解决方案 3:请教专家

找到该领域的专家并帮助他/她了解/改进解决方案。

专业人士:将比自己学习 COM 的来龙去脉更快地获得解决方案 缺点:昂贵但成功的机会很高。还会发现我们甚至不知道的问题。

...

我想你看到了模式。老板做出错误的决定是因为我们(心甘情愿地)让他们做决定。世界上的任何老板在必须做出决定时都会对确凿的事实和投入感到高兴(好吧,那些不应该成为老板的人,所以这是知道何时开始寻找新工作的可靠方法) .

如果您选择解决方案 #2,请选择包装方法。 See the docs 如何编写装饰器 (example from IBM)。包装所有方法只需几分钟的工作,它会给你一些工作。

下一步是创建一个有时会失败的较小示例,然后在此处发布有关 Python、Excel 和 COM 包装器的具体问题,以找出问题的原因。

[EDIT2]下面是一些将“危险”部分包装在帮助器类中并让更新样式更加简单的代码:

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 )
    ...

【讨论】:

  • 我认为这个词并不像你想象的那样。总有一个原因,它往往不是“明显的”。
  • @Spencer Rathbun:“Mngt”?你是说“管理”吗?您是说“管理层希望在一天之内完成,而不是在我学习 com 和调试 excel 之后”?你是说管理层想要一些可能永远无法正常工作的错误和不可靠的东西吗?这就是他们的要求吗?这听起来不像是一个合理的要求。
  • @S.Lott - 有时你不能说在那个时候不能完成,有时一个错误的、不可靠的解决方案正是管理层所需要的——就术语而言只有程序员清楚。也就是说,是的,这是错误的做法,有时您必须做错事才能保住工作,而您却在寻找另一份工作。
  • @Spencer:你错过了一个关键点:你的老板有责任做出决定,但你的有责任给她一个选项列表以及优点/缺点这样她就可以做出合理的决定。只是坐在那里说“我无能为力”会让你陷入你试图避免的麻烦。
  • @Aaron Digulla,我同意你的观点,但我似乎误解了我的立场。 已经做出决定,它现在就开始运作,我们不在乎如何。这是一个比前一个更好的解决方案,它涉及让 excel 宏基于文件名神奇地启动。所以我的代码本来是打开文件,另存为excel,关闭。最后,看在上帝的份上!我正在添加网格线,仅此而已!如果 excel 不完全满意,并告诉我某些行不起作用,我真的不在乎。
【解决方案3】:

这只是包装函数调用,但您也可以对其进行扩展以处理属性访问,并代理嵌套属性访问的结果,最后只需将 __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

【讨论】:

    【解决方案4】:

    您可以从三个列表中压缩参数,然后执行以下操作:

    for border, attr, value in myArgs:
        while True:
            i = 0
            try:
                setattr(excel.Selection.Borders(border), attr, value) 
            except:
                if i>100:
                    break
            else:
                break
    

    如果您的异常是真正随机的,这将一直尝试直到成功(尝试次数限制为 100 次)。我不推荐这个。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-04-10
      • 2011-04-08
      • 1970-01-01
      • 1970-01-01
      • 2015-08-26
      • 1970-01-01
      • 2011-09-29
      相关资源
      最近更新 更多