我需要确定Excel是否已准备好从Python中的win32com接受COM对象。例如,如果在excel中打开一个对话框,则对win32com函数的任何调用都将导致“被调用者拒绝调用”错误。
通过反复试验,我发现如果Excel(实际上我假设任何Office产品)打开了一个对话框,任何对win32com的调用都将导致错误。
经过相当多的谷歌搜索后,我发现了很多关于自行打开对话框的问题。即,执行Excel.SaveAs()将在工作表上打开一个对话框,直到用户关闭它为止。
在我的情况下,我有一个用户打开了一个对话框,或者在其他方面与Excel交互并让它等待输入。一个简单的开始在公式栏上输入公式将导致win32com函数返回错误。
所以有几个问题:有没有办法确定Excel是否已准备好执行命令?有没有办法告诉哪个盒子是打开的(什么是excel等待?)有没有办法通过win32com关闭盒子...记住,据我所知,我用win32com做的任何事情都会返回错误处于这种状态
我知道我可以尝试一下:catch:但我需要围绕每个win32com函数(此时有很多它们)。我认为这种方法会使代码不必要地冗长而复杂。
我一直在努力解决同样的问题,但现在我已经找到了一个对我有用的解决方案。
我创建了一个包含Excel COM对象的类ComWrapper。它自动包装每个嵌套对象并在ComWrapper中调用,并在它们用作函数调用或赋值给包装对象的参数时解除它们。包装器通过捕获“调用被被调用者拒绝”-exceptions并重试调用直到达到顶部定义的超时来工作。如果达到超时,则最终会在包装器对象之外抛出异常。
对包装对象的函数调用由函数_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.