【问题标题】:How can we create multiple PDF files using different win32com instances?我们如何使用不同的 win32com 实例创建多个 PDF 文件?
【发布时间】:2020-06-04 04:04:45
【问题描述】:

我一直在尝试使用 DispatchEx 创建多个 pdf 文件,但是当我尝试测试我的代码时,它只创建第一个 pdf 文件,而所有其他请求都失败并出现奇怪的错误。我做错了什么和/或如何有效地同时处理多个客户呼叫以生成他们请求的各自的 pdf 文件?

这是我的代码的一部分:

            rundate = "Quote_{:%d%m%Y_%H%M%S%f}".format(datetime.now())
            pythoncom.CoInitialize()
            FILENAME = "D:/packages/abc.pptx"
            APPLICATION = win32com.client.DispatchEx("PowerPoint.Application")
            APPLICATION.Visible = True
            path_ppt = shutil.copy(FILENAME, "D:/{0}.pptx".format(rundate))
            PRESENTATION = APPLICATION.Presentations.Open(path_ppt)
            Slide1 = PRESENTATION.Slides(1)
            Shape1 = Slide1.Shapes(1)
            print(Shape1.AlternativeText)
            for shape in Slide1.Shapes:
                if shape.AlternativeText:
                    print(shape.AlternativeText)
                if shape.HasTextFrame:
                    shape.TextFrame.TextRange.Replace(FindWhat="#abc",ReplaceWhat="THAILAND", WholeWords=False)
                if shape.AlternativeText == "1":
                    shape.Fill.UserPicture("D:/1.jpg")
                if shape.AlternativeText == "2":
                    shape.Fill.UserPicture("D:/2.jpg")
                if shape.AlternativeText == "3":
                    shape.Fill.UserPicture("D:/3.jpg")
            PATH_TO_PDF = "{0}{1}{2}".format(r'd:/',rundate,'.pdf')
            PRESENTATION.SaveAs(PATH_TO_PDF, 32)
            APPLICATION.Quit()
            PRESENTATION.Close()
            PRESENTATION =  None
            APPLICATION = None
            os.remove(path_ppt)

PS - 代码成功创建了与发送给它的请求一样多的 ppt 副本(使用shutil),但是当在短时间内发出多个请求时,win32com 会出错,例如 shape.AlternativeText not found, object does not exist等等

【问题讨论】:

  • 也许它的APPLICATION.quit 与另一个应用程序调度重叠。也许您可以单方面打开应用程序,然后在此工作人员中执行 PRESENTATION = APPLICATION.Presentations.Open(path_ppt) 部分。
  • 您能否进一步说明如何做到这一点?我是使用 win32com 的新手并尽力而为。
  • 即使您可以帮助我了解如何知道一个呼叫是否已经存在,然后让我的客户稍后再试,因为 DispatchEx 似乎不像我理解的那样工作
  • 我已经很久没有完成win32com 了,所以只是猜测。我猜测在一个 ppt 实例中创建多个演示文稿比创建多个 ppt 更好。我发现IDispatchEx 上的文档有点令人困惑。假设APPLICATION 是一个全局变量,而您只执行了一次APPLICATION = win32com.client.DispatchEx("PowerPoint.Application")。然后演示文稿将作为该应用程序中的选项卡来来去去。
  • 如果您的客户很热,您可以使用互斥锁来保护此代码以对其进行序列化...但可能会破坏您所在的任何框架。

标签: python win32com pythoncom


【解决方案1】:

您可以通过在单独的进程中运行此代码来解决 APPLICATION 退出问题。 (忽略同时调用时使用硬编码文件名的风险)。

在您的项目包中创建另一个模块

powerpointer.py

import shutil
import sys
import subprocess as subp

def do_powerpoint(filename):
   """run external copy of self to do powerpoint stuff"""
   # sys.executable is the python.exe you are using, __file__ is the
   # path to this module's source and filename you pass in
   return subp.call([sys.executable, __file__, filename])

def _do_powerpoint(filename):
    rundate = "Quote_{:%d%m%Y_%H%M%S%f}".format(datetime.now())
    pythoncom.CoInitialize()
    APPLICATION = win32com.client.DispatchEx("PowerPoint.Application")
    APPLICATION.Visible = True # I think False is better so you dont see it pop up
    path_ppt = shutil.copy(filename, "D:/{0}.pptx".format(rundate))
    PRESENTATION = APPLICATION.Presentations.Open(path_ppt)
    Slide1 = PRESENTATION.Slides(1)
    Shape1 = Slide1.Shapes(1)
    print(Shape1.AlternativeText)
    for shape in Slide1.Shapes:
        if shape.AlternativeText:
            print(shape.AlternativeText)
        if shape.HasTextFrame:
            shape.TextFrame.TextRange.Replace(FindWhat="#abc",ReplaceWhat="THAILAND", WholeWords=False)
        if shape.AlternativeText == "1":
            shape.Fill.UserPicture("D:/1.jpg")
        if shape.AlternativeText == "2":
            shape.Fill.UserPicture("D:/2.jpg")
        if shape.AlternativeText == "3":
            shape.Fill.UserPicture("D:/3.jpg")
    PATH_TO_PDF = "{0}{1}{2}".format(r'd:/',rundate,'.pdf')
    PRESENTATION.SaveAs(PATH_TO_PDF, 32)
    PRESENTATION.Close()
    APPLICATION.Quit()
    os.remove(path_ppt)

if __name__ == "__main__":
    _do_powerpoint(sys.argv[1]) # will raise error if no parameters

现在,在您的主代码中,import powerpointer 并根据需要调用 powerpointer.do_powerpoint(filename)。它将作为脚本运行自己的模块,并且在该实例中您将只有 1 个应用程序对象。

【讨论】:

  • 感谢 tdlaney 提供代码。我今天试过了,但应用程序给了我“呼叫被被呼叫者拒绝”错误。似乎它无法在同时通话中打开多个应用程序?进行 5 次同时调用时收到的错误:pywintypes.com_error: (-2147418111, 'Call was denied by callee.', None, None) self._oleobj_.InvokeTypes(dispid, 0, wFlags, retType, argTypes, *args), self ._oleobj_.InvokeTypes(dispid, 0, wFlags, retType, argTypes, *args), self._oleobj_.InvokeTypes(dispid, 0, wFlags, retType, argTypes, *args),
  • 出现错误后,我还尝试检查文件是否可读,只有这样代码才能工作,但仍然存在问题。它为所有调用打印 True 这应该意味着文件可用? def _do_powerpoint(filename): print(os.access(filename, os.R_OK)) if os.access(filename, os.R_OK): runPpt(filename) else: t = Timer(30.0, runPpt) t.start()其中 runPpt 包含剩余的 _do_powerpoint 代码
  • 我也通过删除以下行尝试了这段代码,它运行良好: PRESENTATION.Close() APPLICATION.Quit() os.remove(path_ppt) 虽然,这应该意味着即使我们制作了子流程,APPLICATION还是一样的。在尝试删除最后一行之前,我也尝试使用 Dispatch 而不是 DispatchEx,但它仍然给被调用者错误拒绝调用
【解决方案2】:

经过几个小时的工作,我能够通过引入以下更改来解决这个问题:

1) 删除

APPLICATION.Visible = True 

2) 改变

APPLICATION.Presentations.Open(path_ppt) 

PRESENTATION = APPLICATION.Presentations.Open(path_ppt, WithWindow=False, ReadOnly=False) 

3) 删除

APPLICATION.Quit()
PRESENTATION.Close()
PRESENTATION =  None
APPLICATION = None
os.remove(path_ppt) 

问题是如果我使用了

APPLICATION.Quit()
PRESENTATION.Close()
PRESENTATION =  None
APPLICATION = None
os.remove(path_ppt) 

在代码中同时调用,它给出了“调用被被调用者拒绝”错误。当我删除这些代码行时,它能够发布与生成的 PowerPoint 一样多的 PDF,但 PowerPoint 实例仍然存在,文件副本也在那里,占用了系统资源。使用 WithWindow = False 有助于在 PDF 发布后自动关闭 PowerPoint 实例。剩下的唯一问题是 PowerPoint 文件的副本,当系统在您的客户端空闲时,可以在一天结束时将其删除。

PS - 我正在使用 ExportAsFixedFormat 而不是 SaveAs for PDF PRESENTATION.ExportAsFixedFormat(PATH_TO_PDF, 32, PrintRange=None)

【讨论】:

    猜你喜欢
    • 2020-10-22
    • 2011-07-15
    • 1970-01-01
    • 1970-01-01
    • 2016-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多