一个很奇怪的BUG

​ 今天遇到个奇怪的bug, 记录于此

发现Bug

今天在使用python+win32com读取exel表格时,突然遇到一个bug,获取不到exel权限,即程序第一句就出错,后面的内容都无法运行,比如下面写了一个简单的测试:

1
2
3
4
5
6
7
8
import win32com.client

xlApp = win32com.client.Dispatch('Excel.Application') # win32com获取exel应用服务
filename = r'E:\\Programs\\python\\自动化处理EXEL\\test.xlsx'
xlwb = xlApp.Workbooks.Open(Filename=filename) # 获取工作簿
print(xlwb)
print(xlwb.Sheets(1).Cells(1, 1)) # 打印第一张工作表的A1单元格的值
xlApp.Quit()

错误信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Traceback (most recent call last):
File "D:\Program Files\Python39\lib\site-packages\win32com\client\dynamic.py", line 81, in _GetGoodDispatch
IDispatch = pythoncom.connect(IDispatch)
pywintypes.com_error: (-2147221021, '操作无法使用', None, None)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "E:\Programs\python\自动化处理EXEL\读取文件.py", line 298, in <module>
test_001()
File "E:\Programs\python\自动化处理EXEL\读取文件.py", line 252, in test_001
xlApp = win32com.client.gencache.EnsureDispatch('Excel.Application')
File "D:\Program Files\Python39\lib\site-packages\win32com\client\gencache.py", line 524, in EnsureDispatch
disp = win32com.client.Dispatch(prog_id)
File "D:\Program Files\Python39\lib\site-packages\win32com\client\__init__.py", line 95, in Dispatch
dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
File "D:\Program Files\Python39\lib\site-packages\win32com\client\dynamic.py", line 98, in _GetGoodDispatchAndUserName
return (_GetGoodDispatch(IDispatch, clsctx), userName)
File "D:\Program Files\Python39\lib\site-packages\win32com\client\dynamic.py", line 83, in _GetGoodDispatch
IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
pywintypes.com_error: (-2147467262, '不支持此接口', None, None)

开始排查问题

这个玩意儿竟然花了我5h时间!!!

  1. 首先,我通过断点调试找到:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# D:\Program Files\Python39\Lib\site-packages\win32com\client\dynamic.py
def _GetGoodDispatch(IDispatch, clsctx = pythoncom.CLSCTX_SERVER):
# quick return for most common case
if isinstance(IDispatch, PyIDispatchType):
return IDispatch
if isinstance(IDispatch, _GoodDispatchTypes):
try:
IDispatch = pythoncom.connect(IDispatch)
except pythoncom.ole_error:
IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch) # 断点最终停在这里过不去
else:
# may already be a wrapped class.
IDispatch = getattr(IDispatch, "_oleobj_", IDispatch)
return IDispatch

# 进一步看pythoncom.py和pywintypes.py文件,也没看出是啥问题

  1. 开始查找百度

    ​ 在百度上看到此问题都是2017年之前的回答,很显然,大家都放弃win32com读取exel这种老办法了,要不我手头上个别文件需要这个,我也是更喜欢使用pandas和openpyxl来着,哼!

    ​ 大家的答案大概总结为以下几个:

    1
    2
    3
    1. (先说一下,这个是最坑的办法!!!)
    有些人说,第一句话使用下面这个代替,与win32com.client.Dispatch()有点区别,使用中注意就行
    excel_APP = win32com.client.gencache.EnsureDispatch('Excel.Application')

    上面这个方法是坑中之坑!我使用上面那句代替以后,问题依旧在,开始往深渊继续探索!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    2. 有些人说检查EXEL有没有注册dcom服务:
    通过dcom.cnfg和regedit来检测和修改

    3. 有些人说dcom中exel Application的权限设置修改一下,
    通过属性->安全

    4. 有些人说删除gen_py
    位置:C:\Users\XXX\AppData\Local\Temp\gen_py

    5. 有人说重新运行一下makepy.py(需要安装win32gui,里面选择exel服务)
    位置:D:\Program Files\Python39\Lib\site-packages\win32com\client\makepy.py


    ​ 上面这些可能对部分人有用,但是我的程序昨天还好好的啊!当然,这些东西照做了,也没好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    6. 在stackoverflow.com上继续查,查到有人说可能是python和windows系统都得是32位才可以。
    。。。。。
    我昨天好好的大哥!
    我python 3.9 + Win10 Windows 10 企业版 20H 64位 + Office 2016 64

    我试着卸载pywin32,重装,装了最新版pywin32-301.win-amd64-py3.9.exe
    网址:https://github.com/mhammond/pywin32/releases
    也丝毫没用
    此时已到中午吃饭时间,中午吃饭时脑子还是非常的乱!脖子也因为一上午紧盯着屏幕没动过而疼得要命...

    7. 要加一个time.sleep(0.5)已解决加载缓慢的问题
    这个在成功建立xlAPP以后,在后续使用中需要注意加载缓慢的情况,现在我遇到的问题是根本还没开始打开exel应用服务!

    ​ 下午开始深究问题的原因:

    1
    2
    3
    4
    5
    发现网上大家出现的问题试以下几个类型:
    a)环境问题
    b) 第三方库问题
    c) 要读取的.xlsx文件路径、名称等的问题(我开始怀疑我当初是不是这个问题)
    d) win32com.client.Dispatch和win32com.client.gencache.EnsureDispatch这两个的区别,很显然,如果使用的后者,因二者使用的语法大小写不一样,导致makepy.py生成的dcom映射读不到“EXEL.Application”, 我上午踩得坑就是这个

解决bug

看到网上好多人都解决不了问题,放弃了python+win32com这条两路线,然而我不会那么轻易放弃!

经过跟踪源码,发现:

Dispatch()除了gencache.EnsureDispatch()以外,还有两个函数可以替代它,于是开始尝试:

1
DispatchEx()和CDispatch()

前者直接解决了我今天的问题!

1
xlApp = win32com.client.DispatchEx('Excel.Application')

使用这句代替后居然神奇的好了!!!

网上搜了一下区别,也就找到一个讨论 https://oomake.com/question/5339468

大概意思是:

1
2
3
4
5
6
7
如果Excel已打开,使用dispatch将在打开的Excel实例中创建新选项卡;而使用dispatchEx将打开一个新的Excel实例。
DispatchEx可能用于远程访问。

从the pywin32 source,您可以看到DispatchEx尝试返回一个包裹的IDispatchEx接口而不是IDispatch。 (考虑到名字,这并不太令人惊讶。) 你可以在MSDN上查找IDispatchEx,你会发现它是

an extension of the IDispatch interface, supports features appropriate for dynamic languages such as scripting languages.
这个想法是,如果你自动化的东西是一个动态对象(就像你在Python或Javascript中那样的对象),而不是一个静态对象(就像你在C++或Java中拥有的那种对象),Visual基本代码可以访问其动态性质 - 在运行时枚举,添加和删除成员等。 当然pywin32几乎可以完成VB的所有工作,有时你需要更加明确一些。在这种情况下,您需要创建一个DispatchEx,并调用其DeleteMemberByName等方法。

看来DispatchEx是扩展版,能建多个win32com服务对象。

还有个神奇的事情:

当我用 DispatchEx() 成功打开exel以后,再用 Dispach() 和gencache.EnsureDispatch()居然也可以打开了,但过一会儿又打不开了…

不过使用DispatchEx屡试屡爽,没出过问题。

继续探索

针对三个函数的区别,我发现最大的区别就是: DispatchEx() 会新建win32com.client ,即新的客户端服务,我的代码里是新的Excel.Application;而后者则使用当前系统进程中的Excel.Application服务。

那么如果我用任务管理器关闭当前所有Microsoft Excel进程,后两者是不是没办法运行啦?

是滴!!!!

果然运行失败,而且出现的错误提示正是我今天一直在解决的BUG!

所以说,我今天早上第一次出现问题,有可能是文件名错误,跟大可能是我刚开机就运行我的代码,没有Microsoft Excel服务进程,所以用pywin32获取不到Excel.Application服务!

做个实验验证一下?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import win32com.client

def test_01():
xlApp = win32com.client.DispatchEx('Excel.Application')
filename = r'E:\\Programs\\python\\自动化处理EXEL\\test.xlsx'
xlwb = xlApp.Workbooks.Open(Filename=filename)
print(xlwb)
print(xlwb.Sheets(1).Cells(1, 1)) # 打印第一张工作表的A1单元格的值
# xlApp.Quit()

def test_02():
xlApp = win32com.client.Dispatch('Excel.Application')
filename = r'E:\\Programs\\python\\自动化处理EXEL\\test.xlsx'
xlwb = xlApp.Workbooks.Open(Filename=filename)
print(xlwb)
print(xlwb.Sheets(1).Cells(1, 1)) # 打印第一张工作表的A1单元格的值
# xlApp.Quit()

def test_03():
xlApp = win32com.client.gencache.EnsureDispatch('Excel.Application')
filename = r'E:\\Programs\\python\\自动化处理EXEL\\test.xlsx'
xlwb = xlApp.Workbooks.Open(Filename=filename)
print(xlwb)
print(xlwb.Sheets(1).Cells(1, 1)) # 打印第一张工作表的A1单元格的值
# xlApp.Quit()


if __name__ == '__main__':
# test_01()
# test_02()
# test_03()
while(True):
test_01()
# test_02()
# test_03()
pass
实验批次 实验过程 实验结果
利用任务管理器关闭所有Microsoft Excel进程,分别运行test_01()、test_02()、test_03() 只有test_01()成功,后两者得到今天的开头的那个bug,即无法自己创建Excel.Application服务
用鼠标随便点开一个电脑上的.xlsx文件,此时任务管理器中增加一个Microsoft Excel进程。进而分别运行test_01()、test_02()、test_03() test_01()、test_02()、test_03()都可正常运行。任务管理器中的情况:test_01()增加一个进程,后两者不增加进程
用鼠标随便点开一个电脑上的.xlsx文件,此时任务管理器中增加一个Microsoft Excel进程。然后分别无限循环运行test_01()、test_02()、test_03() test_01()、test_02()、test_03()都可正常运行。后两者与实验二一致,只使用现有进程;而test-01()则无限增加进程,出现好多个Microsoft Excel进程。

结果分析:

上述实验及结果显示,我的猜测没错,也让我今天的探索终于有了清晰地答案,终于可以安稳地使用win32com了!!

在以后的使用中,我更想用xlApp = win32com.client.DispatchEx(‘Excel.Application’) 来创建Excel.Application服务,看他样子有可能还可以跨计算机创建实例!

还记得大二时陈家骏教授说过,你害怕bug干嘛?程序人写的,你程序写成怎么样,它给你的反馈就那样!完全没有灵异现象!bug只是设计缺陷而已,而你是设计者,造物者!

对啊,经常看到周围人被奇怪的Bug吓得不敢动代码了,准备就那么妥协;而我是个喜欢钻研问题的本质,尽最大能力解决问题的人!尤其是自己写的代码出的问题,我会更认真钻研。

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2019-2022 PAYIZ
  • |

感谢您的支持😊

支付宝
微信