使用Python处理Excel时出现错误

14

当我的脚本更新一个Excel文件时,如果我同时手动在另一个Excel文件上进行任何其他操作,就会出现错误。我正在使用dispatch。

     from win32com.client import Dispatch

     excel    = Dispatch('Excel.Application')
excel.Visible   = True 

file_name="file_name.xls"
workbook = excel.Workbooks.Open(file_name)
workBook  = excel.ActiveWorkbook
sheet=workbook.Sheets(sheetno)

我遇到了这样的错误:(, com_error(-2147418111, '被调用方拒绝了调用', None, None)

有没有什么方法可以克服这个问题,可以在不出错的情况下更新另一个Excel文件。


1
请包含完整的回溯信息,以便我们可以看到哪一行导致了错误。一些注释:首先,您有两个变量workbook和workBook,它们仅相差一个大写字母。这可能不是一个好主意。其次,没有理由获取ActiveWorkbook,因为您已经在前一行引用了它(打开的工作簿立即处于活动状态)。第三,您没有为文件名指定路径,因此如果您的Python代码和Excel工作表位于不同的目录中,则Open将失败。最后,也许sheetno超出了范围。Excel将工作表保存在基于1的数组中。 - Steven Rumbalski
4个回答

18

我最近也遇到了同样的问题。虽然听起来可能有多个根本原因,但我的情况是由于Python在过快地进行后续调用,特别是在进行外部查询刷新时导致Excel跟不上进度。我通过在大多数调用之间插入 time.sleep() 并增加那些特别漫长的调用的睡眠时间(通常在7-15秒之间)来解决这种间歇性的“调用被被叫拒绝”的错误。这样可以让Excel有足够的时间在Python发出其他命令之前完成每个命令。


8
此错误是因为您调用的COM对象如果已经处理另一个操作,则会拒绝外部调用。没有异步处理调用,行为可能看起来很随机。
根据操作,您将看到pythoncom.com_error或pywintypes.com_error。解决此问题的一种简单(但不太优雅)方法是使用try-except包装COM对象中的调用,并且如果您遇到其中一个访问错误,则重试您的调用。
有关背景,请参见Mark Hammond&Andy Robinson(O'Reilly 2000)的《Python Programming on Win32》第12章摘录的“错误处理”部分。
在Siew Kam Onn的博客文章“Python programming with Excel, how to overcome COM_error from the makepy generated python file”中,还有一些关于Excel的有用信息。

2

我运行了一些密集的Excel表格,这些表格在计算周期运行时经常显示出这个(阻塞)错误。

解决方案是使用for循环。

我提供了我的代码解决方案部分,它可以正常工作:

# it failed, keep trying
attempt_number = 0
reading_complete = False
while reading_complete==False:
    try:
        workbook = xw.Book(file_to_read)
        reading_complete = True
        print('file read...')
    except:
        reading_complete = False
        attempt_number += 1
        print('attempt:', attempt_number)
        if attempt_number > 5:
            print('no good: exiting the process')
            exit()

说明:

  • file_to_read 是 Excel 工作簿的完整路径和名称。
  • attempt_number 变量用于限制尝试次数。

2

我曾经也遇到同样的问题,但现在我已经想出了一个对我有效的解决方案。

我创建了一个名为ComWrapper的类,用它来包装Excel COM对象。当在ComWrapper中调用嵌套对象和函数时,每个对象和函数都会自动进行包装和拆包处理。该包装程序通过捕获“调用被被叫方拒绝”异常并重试调用直到达到顶部定义的超时时间。如果达到超时时间,最后会在包装对象外抛出异常。

针对已包装对象的函数调用将自动包装到一个名为_com_call_wrapper的函数中,魔术就是在这里发生的。

要使其正常工作,只需使用ComWrapper从Dispatch中包装com对象,然后像代码底部那样一样使用即可。如果有问题,请留言。

import win32com.client
from pywintypes import com_error
import time
import logging

_DELAY = 0.05  # seconds
_TIMEOUT = 60.0  # seconds


def _com_call_wrapper(f, *args, **kwargs):
    """
    COMWrapper support function. 
    Repeats calls when 'Call was rejected by callee.' exception occurs.
    """
    # Unwrap inputs
    args = [arg._wrapped_object if isinstance(arg, ComWrapper) else arg for arg in args]
    kwargs = dict([(key, value._wrapped_object)
                   if isinstance(value, ComWrapper)
                   else (key, value)
                   for key, value in dict(kwargs).items()])

    start_time = None
    while True:
        try:
            result = f(*args, **kwargs)
        except com_error as e:
            if e.strerror == 'Call was rejected by callee.':
                if start_time is None:
                    start_time = time.time()
                    logging.warning('Call was rejected by callee.')

                elif time.time() - start_time >= _TIMEOUT:
                    raise

                time.sleep(_DELAY)
                continue

            raise

        break

    if isinstance(result, win32com.client.CDispatch) or callable(result):
        return ComWrapper(result)
    return result


class ComWrapper(object):
    """
    Class to wrap COM objects to repeat calls when 'Call was rejected by callee.' exception occurs.
    """

    def __init__(self, wrapped_object):
        assert isinstance(wrapped_object, win32com.client.CDispatch) or callable(wrapped_object)
        self.__dict__['_wrapped_object'] = wrapped_object

    def __getattr__(self, item):
        return _com_call_wrapper(self._wrapped_object.__getattr__, item)

    def __getitem__(self, item):
        return _com_call_wrapper(self._wrapped_object.__getitem__, item)

    def __setattr__(self, key, value):
        _com_call_wrapper(self._wrapped_object.__setattr__, key, value)

    def __setitem__(self, key, value):
        _com_call_wrapper(self._wrapped_object.__setitem__, key, value)

    def __call__(self, *args, **kwargs):
        return _com_call_wrapper(self._wrapped_object.__call__, *args, **kwargs)

    def __repr__(self):
        return 'ComWrapper<{}>'.format(repr(self._wrapped_object))


_xl = win32com.client.dynamic.Dispatch('Excel.Application')
xl = ComWrapper(_xl)

# Do stuff with xl instead of _xl, and calls will be attempted until the timeout is
# reached if "Call was rejected by callee."-exceptions are thrown.

我在这里给一个新问题提供了同样的答案: https://dev59.com/N7Hma4cB1Zd3GeqPMYDO#55892457


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接