使用Python处理Excel时出错
我的脚本在更新一个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文件呢?
4 个回答
我之前也一直在为同样的问题苦恼,但现在我找到了一种对我有效的解决方案。
我创建了一个叫做 ComWrapper 的类,用来包装 Excel 的 COM 对象。这个类会自动处理每一个嵌套的对象和调用,并在使用这些对象作为函数参数或赋值时进行解包。这个包装器的工作原理是捕捉到“调用被被调用者拒绝”的异常,然后重试调用,直到达到顶部定义的超时时间。如果超时了,异常就会在包装器外部抛出。
对被包装对象的函数调用会自动通过一个名为 _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://stackoverflow.com/a/55892457/2828033
这个错误发生的原因是你调用的COM对象在处理其他操作时,会拒绝外部的调用。也就是说,它不能同时处理多个请求,这种情况可能看起来有点随机。
根据你进行的操作,你可能会看到pythoncom.com_error或者pywintypes.com_error。一个简单(虽然不太优雅)的方法来解决这个问题是,把你对COM对象的调用放在try-except块里,如果遇到这些访问错误,就重试你的调用。
想了解更多背景知识,可以参考Mark Hammond和Andy Robinson在《Python Programming on Win32》第12章中的“错误处理”部分,链接在这里:http://oreilly.com/catalog/pythonwin32/chapter/ch12.html。
另外,关于Excel的具体信息,可以查看Siew Kam Onn的博客文章,标题是“Python编程与Excel,如何克服来自makepy生成的Python文件的COM_error”,链接在这里:https://web.archive.org/web/20130529124329/http://www.onnraves.com/2009/05/20/python-programming-with-excel-how-to-overcome-com_error-from-the-makepy-generated-python-file/。
我最近也遇到了这个问题。虽然听起来可能有很多原因,但我的情况是因为Python发出的后续请求太快,Excel来不及处理,特别是在刷新外部查询的时候。我通过在大多数调用之间插入time.sleep()
来解决这个偶尔出现的“调用被拒绝”的错误,并且对于那些特别耗时的调用(通常需要7到15秒),我还增加了等待的时间。这样可以给Excel足够的时间来完成每个命令,然后Python再发出新的命令。