【问题标题】:Multiprocessing: Use Win32 API to modify Excel-Cells from Python多处理:使用 Win32 API 从 Python 修改 Excel-Cells
【发布时间】: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


【解决方案1】:

虽然 Excel 在重新计算电子表格时可以使用多个内核,但其编程接口与单线程的 UI 模型紧密相关。活动的工作簿、工作表和选择都是单例对象;这就是为什么您不能在使用 COM(或 VBA,就此而言)驱动 Excel UI 的同时与它进行交互。

tl;博士

Excel 不能那样工作。

【讨论】:

  • 好的,但是:是的,但实际上我不需要使用多个 Excel 对象,我什至更愿意只使用一个 Excel 对象,但我不知道如何“共享”该对象是所有单个进程的对象,因为它是不可挑选的......不过就足够了
  • 共享它没有任何意义,因为无论如何一次只能有一个进程使用它。如果您为 Excel 准备数据做了大量工作,您可以将其传递给子流程,然后将其从主流程推送到工作簿中。如果您的数据准备并不重要,那么最好只从一个进程/线程进行(因为与子进程通信的开销大于收益)。
  • 好的,再澄清一点:写入 excel 只是每个子流程中非常非常小的一部分。每个进程都从 Internet 请求一些 HTTP 页面,因此这部分是我想要并行化的最大性能影响。因此,即使只有一个进程可以同时写入 Excel,这也很好,因为与每个进程的总运行时间相比,其他进程完成 Excel 写入的等待时间只会产生轻微的开销。
  • 在这种情况下,将工作传递给子进程并让它们将 Excel 的数据返回给具有 xls 对象的主进程。这种情况确实有道理,而且您只有一个进程与 Excel 对话(无论如何这是唯一可行的方法)。
  • 好吧,这仍然需要更多的返工,但可能是代码维护之类的最佳方式。 编辑:线程真的不能替代吗?因为根据我的阅读和理解:线程都在同一个进程中运行,所以都会知道单个对象xls,因此可以使用它!?好的,我知道对于编写 Cells() 的多个并发调用,这可能会相互干扰或阻塞单个线程,但也可以在写入过程之前和之后锁定/解锁线程..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-25
  • 1970-01-01
  • 2013-03-29
  • 1970-01-01
  • 2011-06-27
  • 2021-11-15
  • 1970-01-01
相关资源
最近更新 更多