Python 清晰地将单个语句包装在 try except 块中
我现在正在用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 个回答
这段话的意思是,这个代码主要是用来包装函数调用的,但你可以把它扩展一下,让它也能处理属性的访问。也就是说,它可以帮助你处理那些嵌套的属性访问,最后只需要把__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
异常的发生绝对不是“没有明显原因”的。总是有原因存在,而这个原因需要被解决。否则,你的程序就会开始产生“随机”的数据,而这种“随机”是由你隐藏的错误所决定的。
当然,你需要一个解决方案来处理你的问题。以下是我的建议:
创建一个包装类,里面实现你需要的所有方法,并把这些方法委托给真实的Excel实例。
在每个方法前加一个装饰器,把这个方法放在一个
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 )
...
考虑把抑制异常的部分抽象出来。正如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')
这样一来,你的代码会逐行执行(这样错误追踪信息仍然有用),而且你只会抑制那些你明确想要抑制的异常。其他的异常会像往常一样继续传播。