Python 将单个语句包装在 try except 块中的简洁方法

发布于 2024-12-02 14:58:23 字数 3460 浏览 1 评论 0原文

我目前正在使用 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 行引发异常。另一个为每个 xlInsideHorizo​​ntal 抛出异常。最后,第三次运行完成,没有任何异常。

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

它将与包含在 try except 块中的每一行一起运行,我只是想要一些更短、更明显的内容,因为包含在自己的 try catch 循环中的 20 行只是在以后自找麻烦。

更新2

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

结论

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

I'm currently doing some Python automation of Excel with COM. It's fully functional, and does what I want, but I've discovered something surprising. Sometimes, some of the Excel commands I use will fail with an exception for no apparent reason. Other times, they will work.

In the VB equivalent code for what I'm doing, this problem is apparently considered normal, and is plastered over with a On Error Resume Next statement. Python does not have said statement, of course.

I can't wrap up the whole set in a try except loop, because it could "fail" halfway through and not complete properly. So, what would be a pythonic way to wrap several independent statements into a try except block? Specifically, something cleaner than:

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

The relevant code is the excel.Selection.Borders bit.

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

Update:

Perhaps a bit of explanation on the error bit is required. Two identical runs on my test machine, with identical code, on the same file, produce the same result. One run throws exceptions for every xlInsideVertical line. The other throws exceptions for every xlInsideHorizontal. Finally, a third run completes with no exceptions at all.

As far as I can tell Excel considers this normal behavior, because I'm cloning the VB code built by Excel's macro generator, not VB code produced by a person. This might be an erroneous assumption, of course.

It will function with each line wrapped in a try except block I just wanted something shorter and more obvious, because 20 lines wrapped in their own try catch loops is just asking for trouble later.

Update2:

This is a scrubbed CSV file for testing: gist file

Conclusion:

The answer provided by Vsekhar is perfect. It abstracts away the exception suppression, so that later, if and when I have time, I can actually deal with the exceptions as they occur. It also allows for logging the exceptions so they don't disappear, not stopping other exceptions, and is small enough to be easily manageable six months from now.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

陌上芳菲 2024-12-09 14:58:23

考虑消除压制。就亚伦的观点而言,一般情况下不要接受例外情况。

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

这样您就可以逐行执行(因此您的回溯仍然会有帮助),并且您只抑制您明确想要的例外情况。其他异常照常传播。

Consider abstracting away the suppression. And to Aaron's point, do not swallow exceptions generally.

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

Then, note in the traceback of your current code exactly what exception is raised, and create a Suppressor for just that exception:

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

This way you get line-by-line execution (so your tracebacks will still be helpful), and you are suppressing only the exceptions you explicitly intended. Other exceptions propagate as usual.

只是在用心讲痛 2024-12-09 14:58:23

例外永远不会“没有明显原因”发生。总有一个原因,而这个原因需要解决。否则,您的程序将开始生成“随机”数据,其中“随机”数据受您隐藏的错误的支配。

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

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

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

现在,代码可以为您的客户工作,这为您赢得了一些时间来找出问题的原因。我的猜测是:a) Excel 不会生成有用的错误消息,或者 b) 包装器代码吞掉了真正的异常,让您一无所知,或者 c) Excel 方法返回错误代码(例如“失败”的“false”)并且您需要调用另一个Excel方法来确定问题的原因是什么。

[编辑]基于下面的评论,归结为“我的老板不在乎,我无能为力”:你错过了一个关键点:这是你老板的职责 做出决定,但有责任给她一份选项清单以及优点/缺点,以便她能够做出明智的决定。只是坐在那里说“我无能为力”会让你陷入你试图避免的麻烦。

示例:

解决方案 1:忽略错误

优点:工作量最少
缺点:生成的数据有可能是错误的或随机的。如果重要的业务决策以此为基础,那么这些决策出错的风险就很高。

解决方案 2:记录错误

优点:工作量很小,用户可以快速开始使用结果,为找出问题根源赢得时间
反对者:“如果你今天不能解决它,你凭什么认为明天就有时间解决它?”另外,您可能需要很长时间才能找到问题的根源,因为您不是专家。

解决方案 3:询问专家

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

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

...

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

如果您选择解决方案#2,请采用包装器方法。 查看文档如何编写装饰器 (< a href="http://www.ibm.com/developerworks/linux/library/l-cpdecor/index.html" rel="noreferrer">来自 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 )
    ...

Exceptions never happen "for no apparent reason". There is always a reason and that reason needs to be fixed. Otherwise, your program will start to produce "random" data where "random" is at the mercy of the bug that you're hiding.

But of course, you need a solution for your problem. Here is my suggestion:

  1. Create a wrapper class that implements all the methods that you need and delegates them to the real Excel instance.

  2. Add a decorator before each method which wraps the method in a try except block and log the exception. Never swallow exceptions

Now the code works for your customer which buys you some time to find out the cause of the problem. My guess is that a) Excel doesn't produce a useful error message or b) the wrapper code swallows the real exception leaving you in the dark or c) the Excel method returns an error code (like "false" for "failed") and you need to call another Excel method to determine what the cause of the problem is.

[EDIT] Based on the comment below which boil down to "My boss doesn't care and there is nothing I can do": You're missing a crucial point: It's your bosses duty to make the decision but it your duty to give her a list of options along with pros/cons so that she can make a sound decision. Just sitting there saying "I can't do anything" will get you into the trouble that you're trying to avoid.

Example:

Solution 1: Ignore the errors

Pro: Least amount of work
Con: There is a chance that the resulting data is wrong or random. If important business decisions are based on it, there is a high risk that those decisions will be wrong.

Solution 2: Log the errors

Pro: Little amount of work, users can start to use the results quickly, buys time to figure out the source of the problem
Con: "If you can't fix it today, what makes you think you will have time to fix it tomorrow?" Also, it might take you a long time to find the source of the problem because you're no expert

Solution 3: Ask an expert

Find an expert in the field and help him/her have a look/improve the solution.

Pro: Will get a solution much more quickly than learning the ins and outs of COM yourself
Con: Expensive but high chance of success. Will also find problems that we don't even know about.

...

I think you see the pattern. Bosses make wrong decisions because we (willingly) let them. Any boss in the world is happy for hard facts and input when they have to make a decision (well, those who don't shouldn't be bosses, so this is a surefire way to know when to start looking for a new job).

If you select solution #2, go for the wrapper approach. See the docs how to write a decorator (example from IBM). It's just a few minutes of work to wrap all the methods and it will give you something to work with.

The next step is to create a smaller example which sometimes fails and then post specific questions about Python, Excel and the COM wrapper here to figure out the reason for the problems.

[EDIT2] Here is some code that wraps the "dangerous" parts in a helper class and makes updating the styles more simple:

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

Usage:

    borders = BorderHelper( excel )

    borders.set( xlDiagonalDown, LineStyle = xlNone )
    borders.set( xlDiagonalUp, LineStyle = xlNone )
    borders.set( xlEdgeLeft, LineStyle = xlContinuous, Weight = xlThin, Color = xlAutomatic )
    ...
逆蝶 2024-12-09 14:58:23

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

This just wraps functions calls, but you can extend it to handle attribute access as well, and to proxy the results of nested attribute accesses, finally just wrapping the __setattr__ in your try:except block.

It might be sensible to swallow only some specific exception types in your case (as @vsekhar says).

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)

Example:

exceptionProofBorders = onErrorResumeNext(excel.Selection.Borders)
exceptionProofBorders(xlDiagonalDown).LineStyle = xlNone
exceptionProofBorders(xlDiagonalup).LineStyle = xlNone
墟烟 2024-12-09 14:58:23

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

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 次尝试)。我不推荐这个。

You can zip arguments from three list, and do the following:

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

If your exceptions are trully random, this will try until success (with a limit of 100 tries). I don't recommend this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文