Python多线程使用win32com注意事项

  • 发布时间:2016年7月12日 11:41
  • 作者:杨仕航
  • 分类标签: Python
  • 阅读(18714)
  • 评论(1)

这是一件“悲哀”的事情,说出来给大家乐乐。

由于项目需要,需要用Python对word文档进行读写。我没多想,屁颠屁颠到pypi找了word操作库——python-docx。

刚开始用觉得还挺好用。后来随着项目深入,需要处理的word文件越来越大。


这个python-docx库的肩膀太小,负担不起。

因为它处理的东西都是放在内存里面,而且每次读写好像要跑个马拉松。文件越大,效率越慢。


没办法,我只能采用win32com组件调用word的com组件的方案。这个方案需要系统环境是windows,而且需要安装Office。所以开始就没考虑这个。(win32com组件可以在https://sourceforge.net/projects/pywin32/files/pywin32下载,我选的是220版本。根据系统和Python版本下载了64位的python2.7那个)


本篇博文,主要不是说如何用win32com读写word文档。

用win32com对word操作和写vba代码差不多,语法基本一致。简单贴上win32com调用word示例代码:

#coding:utf-8
from win32com.client import Dispatch

def test(doc_full_path):
    table_count = 0
    try:
        word = Dispatch('word.application')
        doc = word.documents.Open(doc_full_path)

        #获取该文档的表格数
        table_count = doc.tables.count
    
        #关闭
        doc.Close()
    except Exception as e:
        print(e.message)
    finally:
        #对com操作,一定要确保退出word应用
        if word:
            word.Quit()
        return table_count

上面的test方法是给定word文档的路径,获取该文档里面表格的数量。


我在cmd命令行测试,发现word.Quit()执行之后,word的进程还存在!

我还可以重新再将这个应用显示出来。为了保险起见,加了一句删除变量的代码,才真正退出word进程。

del(word)

不过一般在程序中执行没有这个问题。因为该方法执行完成之后,会自动清理内存,删除没有使用的变量等。


单单这段代码测试一点问题都没有。但放入到总程序里面就出现问题了!返回错误信息说没有CoInitialize初始化。

我写的总程序是一个总控制脚本,里面用到多线程。我就怀疑是不是多线程导致的问题。

搜索一下,发现确实和多线程有关系。

在多线程里面使用win32com调用com组件的时候,需要用pythoncom.CoInitialize初始化一下。

最后还需要用pythoncom.CoUninitialize释放资源。


上面代码改成这样:

#coding:utf-8
from win32com.client import Dispatch
import pythoncom

def test(doc_full_path):
    table_count = 0
    pythoncom.CoInitialize()
    try:
        word = Dispatch('word.application')
        doc = word.documents.Open(doc_full_path)

        #获取该文档的表格数
        table_count = doc.tables.count
    
        #关闭
        doc.Close()
    except Exception, e:
        print e.message
    finally:
        #对com操作,一定要确保退出word应用
        if word:
            word.Quit()
        
        #释放资源
        pythoncom.CoUninitialize()
        return table_count

修改之后,无论单独执行还是放到多线程里面执行都没问题了。


那这个CoInitialize是何方神圣?加了这个东西之后,在多线程就可以正常运行。

Coinitialize是Windows提供的API函数(好吧,还是微软惹的“祸”),用来告诉Windows系统单独一个线程创建COM对象。也就是说我这个多线程的脚本里面的线程和这个COM对象的线程创建一个套间,令其可以正常关联和执行。

看不懂?好吧,我也不是很清楚原理。反正,多线程使用win32com,就得加上这个。不喜欢Windows系统,问题多多。


另外,还有一个坑。

若我先手动打开一个Word文件,这时就运行了一个word进程。若执行Dispatch创建进程。

word = Dispatch('word.application')

再设置word不可见。

word.visible = False

但,我手动打开的Word进程的界面被隐藏了。

这说明一个问题,Dispatch若发现该进程已经存在的话,就不会创建新的进程。

若不创建新的进程,有些操作会有冲突,可能会影响到已经打开的Word文件。

这时,不能再使用Dispatch,用如下代码:

from win32com.client import DispatchEx
word = DispatchEx('word.application')

即可真正创建新进程,无论是否已经打开了一样的进程。

上一篇:vb/vba抽取不重复随机数

下一篇:我的网站搭建(第22天) 分页器优化

评论列表

杨仕航

杨仕航

2017-02-13更新本文,添加两个使用的注意事项

2017-02-13 09:28 回复

新的评论

清空