Python-win32com 的 Excel COM 模型开始产生错误。

30
在过去的几天里,我一直在自动化生成一些报告的数据透视表。简而言之,以下代码正常工作:
import win32com.client    
objExcelApp = win32com.client.gencache.EnsureDispatch('Excel.Application')
objExcelApp.Visible = 1

这将弹出一个Excel实例,我可以继续在Python中工作。但突然间,今天我的脚本遇到以下问题:

>>>import win32com.client
>>> objExcelApp = win32com.client.gencache.EnsureDispatch('Excel.Application')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Program Files (x86)\Python37-32\lib\site-packages\win32com\client\gencache.py", line 534, in EnsureDispatch
    mod = EnsureModule(tla[0], tla[1], tla[3], tla[4], bForDemand=bForDemand)
  File "C:\Program Files (x86)\Python37-32\lib\site-packages\win32com\client\gencache.py", line 391, in EnsureModule
    module = GetModuleForTypelib(typelibCLSID, lcid, major, minor)
  File "C:\Program Files (x86)\Python37-32\lib\site-packages\win32com\client\gencache.py", line 266, in GetModuleForTypelib
    AddModuleToCache(typelibCLSID, lcid, major, minor)
  File "C:\Program Files (x86)\Python37-32\lib\site-packages\win32com\client\gencache.py", line 552, in AddModuleToCache
    dict = mod.CLSIDToClassMap
AttributeError: module 'win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x9' has no attribute 'CLSIDToClassMap'

代码从昨天到今天没有改变。我不知道发生了什么事!!!

另一个有趣的插曲。如果我在同一会话中再次执行相同的代码,我会得到不同的错误:

>>> objExcelApp = win32com.client.gencache.EnsureDispatch('Excel.Application')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Program Files (x86)\Python37-32\lib\site-packages\win32com\client\gencache.py", line 534, in EnsureDispatch
    mod = EnsureModule(tla[0], tla[1], tla[3], tla[4], bForDemand=bForDemand)
  File "C:\Program Files (x86)\Python37-32\lib\site-packages\win32com\client\gencache.py", line 447, in EnsureModule
    if module.MinorVersion != tlbAttributes[4] or genpy.makepy_version != module.makepy_version:
AttributeError: module 'win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x9' has no attribute 'MinorVersion'
>>>

所以我切换到一个安装了最新Windows系统的Windows机器上,安装了Python37并pip安装pypiwin32。运行完全相同的代码,Excel就像昨天在我的原始机器上一样打开。

我尝试过卸载和重新安装,但没有任何效果。这里有什么想法吗?

注意:动态分发仍然有效:

import win32com.client
objExcelApp = win32com.client.Dispatch("Excel.Application")
objExcelApp.Visible = 1

但我需要使用静态分派,因为数据透视表不支持动态分派的对象(在我的代码更后面):

objExcelPivotCache = objExcelWorkbook.PivotCaches().Create(SourceType=win32c.xlDatabase, SourceData=objExcelPivotSourceRange)
7个回答

55

我曾经遇到过同样的问题,但是我按照这里的指示解决了它:https://mail.python.org/pipermail/python-win32/2007-August/006147.html

删除 gen_py 输出目录并重新运行 makepy 成功,随后测试应用程序再次正常运行。

因此,症状得到了解决,但是有什么线索可以说明这是如何发生的吗?这是一个非常长时间运行的应用程序(想象一下 24x7 运行多年),我担心会再次发生类似的情况。

要查找输出目录,请在您的 Python 控制台 / Python 会话中运行以下命令:

import win32com
print(win32com.__gen_path__)

或者,更好的是,在命令行中使用一行代码:

python -c "import win32com; print(win32com.__gen_path__)"

根据您发布的异常消息,您需要删除的目录将被命名为'00020813-0000-0000-C000-000000000046x0x1x9'。因此,请删除此目录并重新运行代码。如果您担心删除它(就像我一样),只需剪切该目录并将其粘贴到其他地方即可。

请注意,此目录通常位于您的“TEMP”目录中(在Windows文件资源管理器中复制并粘贴%TEMP%/gen_py,即可直接到达该目录)。

我不知道为什么会出现这种情况,也不知道如何防止它再次发生,但是我提供的链接中的说明似乎对我有效。


3
就我而言,路径为"C:\Users<我的用户名>\AppData\Local\Temp\gen_py"。FWIW表示"For What It's Worth",可译为“供参考”。请注意,翻译过程中要保留原意,使语言简单易懂。 - Rafael Zayas
1
@RafaelZayas,是的,请导航到“C:\ Users <your username> \ AppData \ Local \ Temp \ gen_py”,在该目录中的某个位置,您将找到一个与AttributeError消息中显示的名称相同的文件夹(根据原始帖子,op应删除名为'00020813-0000-0000-C000-000000000046x0x1x9'的文件夹)。因此,请查找那个奇怪命名的目录,将其删除,然后重新运行您的代码。 - Ian
5
如果您想知道在删除gen_py目录后如何运行makepy,请导航到C:<python安装目录>\Lib\site-packages\win32com\client,然后运行makepy.py。请注意,本文不包括任何解释。 - Jason

18

一个更为直接的解决方案已经在一个相关问题中发布Issue in using win32com to access Excel file

基本上,您只需要删除文件夹C:\Users\<your username>\AppData\Local\Temp\gen_py并重新运行代码。

TIP:您还可以在Windows文件浏览器中输入 %TEMP%\gen_py 直接访问它,然后删除其内容。


它对我有效!我遇到了 module 'win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x9' has no attribute 'CLSIDToClassMap' 错误。删除 gen_py 文件夹后,问题解决了。 - etoricky
我的gen_py文件夹里没有子文件夹,我会按照你的建议进行删除。谢谢! - Quy Nguyen
谢谢,对我来说非常顺利。我的Python代码在后台打开了Excel,将.xlsx文件转换为pdf。我将我的代码分享为.exe文件,但对方没有清除她的临时文件!这是软件工程师需要解决的错误(有意或无意的)! - undefined

10

在powershell或cmd中执行此命令行(不要在管理员模式下执行 => 对我无效)

   python -m win32com.client.makepy "Excel.Application"

它可以修复所有错误,您不需要更改Python代码。并且继续使用。
win32com.client.gencache.EnsureDispatch("Excel.Application")

使用gencache.EnsureDispatch可以访问由makepy动态加载的应用程序的常量,该应用程序必须具有注册的应用程序(在我们的情况下,是Excel.Application)。 如果您在Outlook上遇到同样的问题,请使用“Outlook.Application”。

如果仍然无法工作,请重新安装您的Python发行版的pywin32。

<path to python root or venv>\pip.exe uninstall pywin32
<path to python root or venv>\pip.exe install pywin32

1
对于Python 3.7,这是有效的。谢谢。 - Ozgur
1
卸载对我有用!谢谢!!! - Demi Dee

3
我成功的经验是:
excel = win32.gencache.EnsureDispatch('Excel.Application')
#change to =>
excel = win32.Dispatch('Excel.Application')

1
你的意思是使用win32com.client.Dispatch。只有在不使用win32com.client.constants时才有效,这就是Chris所描述的问题... - Julienm
我通过谷歌搜索类似的问题来到这篇文章,但在我的情况下,我不关心可选参数 - pywin32不接受它们。当然,我本可以在之前就知道这个更简单的形式,但学习永远不嫌晚。(我还猜想,在瓷砖示例中,Dispatch也可以用于数据透视表,只需更改默认顺序的可选参数的使用方式即可)。 - ChrCury78
这对你有效,但并不适用于所有人:请查看这里。特别是段落Forcing Early or Late Binding。简而言之,Nikita提出的方法并不确定性。你让Python自动选择绑定(错误的来源),谁知道它是否选择正确 :) - z33k

1
对我来说,问题似乎在于我有多个进程通过win32com与Windows应用程序交互。由于win32comwin32api.GetTempPath()中创建了"gen_py"目录,这可能会导致冲突和缓存损坏。我的解决方案是为每个进程设置自定义位置的“gen_py”。以下是一个简单的示例:
from pathlib import Path
import win32com

gen_py_path = '/some/custom/location/gen_py'

Path(gen_py_path).mkdir(parents=True, exist_ok=True)
win32com.__gen_path__ = gen_py_path

# Any other imports/code that uses win32com

这样,您就不必删除默认的“gen_py”文件夹,并担心可能会出现什么问题。但是如果您仍然发现需要删除,则可以只删除自定义文件夹,并知道您正在仅为该进程删除缓存。

0

为了补充这个讨论,对于那些作为无人看守的自动化流程的一部分而接收到此错误的人,您可以完全自动化恢复过程,并允许任何进程在无人看守的情况下继续。

正如Ian和Qin所提到的那样,我们需要删除gen_py输出目录并重新启动进程。就自动化而言,这里有两个问题:`gen_py`输出目录是一个临时目录,可能会更改,依赖`gen_py`的当前运行进程即使在重新生成后仍然失败。

为了解决这些问题,我们可以动态查找位置,然后完全清除整个进程并重新启动它。我尝试过删除和重新导入win32com,但似乎仍然保留了对已损坏缓存的引用,因此整个进程需要重新启动。

temp_data_dir = os.environ.get('LOCALAPPDATA'))
gen_py_dir = ''
for curr_path, dirs, files, in walk(temp_data_dir)
    if 'gen_py' in dirs:
        gen_py_dir = Path(curr_path).joinpath('gen_py')

shutil.rmtree(gen_py_dir)
execv(restart_args)

重新启动的进程应该再次调用win32com.client.gencache.EnsureDispatch('Excel.Application'),然后将使用新的gen_py副本,然后我们可以重试失败的Excel自动化代码。
这个问题会不可预测地偶尔发生,我无法复制。我最好的猜测是gen_py缓存有时会以某种方式损坏,只需要刷新即可。

不,它没有损坏。我已经删除了文件夹几次,以便创建一个全新的文件夹。但是没有成功。 - beachgreatsquaremoon
你可以查看我的答案,以找到一个不需要重新启动流程的解决方案。 - Harkan

0
我尝试过删除并重新导入win32com,但似乎仍然保留了对损坏缓存的引用,所以整个过程需要重新启动。
补充Daynil的答案,我成功地实现了在不重新启动整个过程的情况下自动化恢复过程。我深入研究了win32com.client.gencache的代码,发现只需删除sys.modules对有问题模块的引用,就可以让gencache在第二次Dispatch时重新构建文件。
以下是我提议的代码:
import win32com.client, shutil
from pathlib import Path

for attempt in range(2):
        try:
            excel = win32com.client.gencache.EnsureDispatch('Excel.Application')
            
        except AttributeError as e:
            if e.name == 'CLSIDToClassMap':
                mod_name = e.obj.__name__
                mod_name_parts = mod_name.split('.')
                if len(mod_name_parts) == 3:
                    # Deleting the problematic module cache folder
                    folder_name = mod_name_parts[2]
                    gen_path = Path(win32com.__gen_path__)
                    folder_path = gen_path.joinpath(folder_name)
                    shutil.rmtree(folder_path)
                    # Deleting the reference to the module to force a rebuilding by gencache
                    del sys.modules[mod_name]
                    continue

            raise Exception("There was an error loading Excel.") from e

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