【发布时间】:2016-10-15 18:42:37
【问题描述】:
我真的在这里寻找一个好的解决方案,也许我如何做或最后尝试做的完整概念是错误的!?
我想让我的代码能够使用我所有的内核。在代码中,我正在使用 Win32 API 修改 Excel 单元格。我写了一个小的 xls-Class,它可以检查所需的文件是否已经打开(或者如果没有打开它)并将值设置为单元格。我的精简代码如下所示:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import win32com.client as win32
from multiprocessing import Pool
from time import sleep
class xls:
excel = None
filename = None
wb = None
ws = None
def __init__(self, file):
self.filename = file
def getNumOpenWorkbooks(self):
return self.excel.Workbooks.Count
def openExcelOrActivateWb(self):
self.excel = win32.gencache.EnsureDispatch('Excel.Application')
# Check whether one of the open files is the desired file (self.filename)
if self.getNumOpenWorkbooks() > 0:
for i in range(self.getNumOpenWorkbooks()):
if self.excel.Workbooks.Item(i+1).Name == os.path.basename(self.filename):
self.wb = self.excel.Workbooks.Item(i+1)
break
else:
self.wb = self.excel.Workbooks.Open(self.filename)
def setCell(self, row, col, val):
self.ws.Cells(row, col).Value = val
def setLastWorksheet(self):
self.ws = self.wb.Worksheets(self.wb.Worksheets.Count)
if __name__ == '__main__':
dat = zip(range(1, 11), [1]*10)
# Create Object
xls = xls('blaa.xls')
xls.openExcelOrActivateWb()
xls.setLastWorksheet()
for (row, col) in dat:
# Calculate some value here (only depending on row,col):
# val = some_func(row, col)
val = 'test'
xls.setCell(row, col, val)
现在循环只依赖于两个迭代变量,我想让它在多个内核上并行运行。所以我听说过线程和多处理,但后者对我来说似乎更容易,所以我试了一下。
所以我把代码改成这样:
import os
import win32com.client as win32
from multiprocessing import Pool
from time import sleep
class xls:
### CLASS_DEFINITION LIKE BEFORE ###
''' Define Multiprocessing Worker '''
def multiWorker((row, col)):
xls.setCell(row, col, 'test')
if __name__ == '__main__':
# Create Object
xls = xls('StockDatabase.xlsm')
xls.openExcelOrActivateWb()
xls.setLastWorksheet()
dat = zip(range(1, 11), [1]*10)
p = Pool()
p.map(multiWorker, dat)
似乎没有工作,因为在阅读了一些内容后,Multiprocessing 启动了新进程,因此工作人员不知道 xls。
不幸的是,我不能将xls 作为第三个参数传递给他们,因为 Win32 不能被腌制:( 像这样:
def multiWorker((row, col, xls)):
xls.setCell(row, col, 'test')
if __name__ == '__main__':
# Create Object
xls = xls('StockDatabase.xlsm')
xls.openExcelOrActivateWb()
xls.setLastWorksheet()
dat = zip(range(1, 11), [1]*10, [xls]*10)
p = Pool()
p.map(multiWorker, dat)
唯一的方法是在定义 multiWorker 之前为每个进程初始化 Win32:
# Create Object
xls = xls('StockDatabase.xlsm')
xls.openExcelOrActivateWb()
xls.setLastWorksheet()
def multiWorker((row, col, xls)):
xls.setCell(row, col, 'test')
if __name__ == '__main__':
dat = zip(range(1, 11), [1]*10, [xls]*10)
p = Pool()
p.map(multiWorker, dat)
但我不喜欢它,因为我的 xls 构造函数有更多逻辑,它会自动尝试查找已知标题子字符串的列 ID...所以这比想要的要多一点努力(而且我不认为每个进程都应该真正打开它自己的 Win32 COM 接口),这也给了我一个错误,因为 gencache.EnsureDispatch 可能无法如此频繁地调用......
怎么办?解决方案如何? 谢谢!!
【问题讨论】:
-
您需要两个步骤。一步是并行收集数据,然后是第二步将其推送到 Excel 中。如果你能避免自动化 Excel 就更好了。如果您只需要生成一个数据文件,那么您根本不需要 Excel 流程。
-
Excel 是绝对需要的,因为它是可视化工具(我正在与通常使用办公室的人一起工作),所以最终数据必须在那里。回答你的问题:所以在多个进程中使用多个 Win32 COM 对象写入同一张工作表是不可能的?我知道这可能不是最好的解决方案,但它会让我在重构我的代码时付出最小的努力。我的意思是首先收集所有数据,最后将其写入 Excel 似乎是最好的解决方案,但是从我上面列出的串行方法来看,您会注意到它需要更多的返工。
-
编辑:是否可以使用线程而不是单个进程,因此可以为所有单个线程使用一个
xls实例? -
没有什么可以阻止您从多个线程使用 COM 对象。只要您确保遵循 COM 的线程规则,通过正确编组进出 STA 的接口。对 COM 对象的单独调用已正确序列化。但是,Excel 不提供更高级别的并发模型,例如事务。除非您在控制器应用程序中实现同步,否则各个调用将相互交错。
-
制作文件不需要excel。
标签: python excel winapi multiprocessing