最近我遇到了完全相同的问题,所以我深入研究了PyPDF2,看看出了什么问题,以及如何解决它。
注意:我假设filename
是一个格式良好的文件路径字符串。我的所有代码都假设相同。
简短回答
使用PdfFileMerger()
类,而不是PdfFileWriter()
类。 我已经尽可能提供了以下内容,以尽量与您的内容相似:
from PyPDF2 import PdfFileMerger, PdfFileReader
[...]
merger = PdfFileMerger()
for filename in filenames:
merger.append(PdfFileReader(file(filename, 'rb')))
merger.write("document-output.pdf")
长篇回答
你使用的 PdfFileReader
和 PdfFileWriter
的方式会使每个文件保持打开状态,最终导致 Python 生成 IOError 24。更具体地说,当你向 PdfFileWriter
添加页面时,你正在向打开的 PdfFileReader
中添加页面的引用(因此如果关闭文件,则会出现 IO 错误)。Python 检测到文件仍然被引用,并且不进行任何垃圾回收/自动文件关闭,尽管重新使用文件句柄。它们一直保持打开状态,直到 PdfFileWriter
不再需要访问它们,即在你的代码中的 output.write(outputStream)
处。
为了解决这个问题,需要在内存中创建内容的副本,并允许文件关闭。我在 PyPDF2 代码中的探险中注意到 PdfFileMerger()
类已经具有此功能,因此我选择使用它而不是重新发明轮子。但是,我发现我的初步查看 PdfFileMerger
不够仔细,它只在特定条件下创建副本。
我的初始尝试如下,结果导致相同的 IO 问题:
merger = PdfFileMerger()
for filename in filenames:
merger.append(filename)
merger.write(output_file_path)
查看 PyPDF2 源代码,我们发现 append()
要求传递 fileobj
,然后使用 merge()
函数,并将其最后一页作为新文件的位置传入。在使用 PdfFileReader(fileobj)
打开之前,merge()
对 fileobj
执行以下操作:
if type(fileobj) in (str, unicode):
fileobj = file(fileobj, 'rb')
my_file = True
elif type(fileobj) == file:
fileobj.seek(0)
filecontent = fileobj.read()
fileobj = StringIO(filecontent)
my_file = True
elif type(fileobj) == PdfFileReader:
orig_tell = fileobj.stream.tell()
fileobj.stream.seek(0)
filecontent = StringIO(fileobj.stream.read())
fileobj.stream.seek(orig_tell)
fileobj = filecontent
my_file = True
我们可以看到
append()
选项确实接受字符串,当这样做时,它会假定它是一个文件路径,并在该位置创建一个文件对象。最终结果与我们试图避免的完全相同。然而,如果我们在将其传递到
append()
之前,将该路径字符串转换为文件对象或
PdfFileReader
(见编辑2)对象中的一个,它将自动为我们创建一个副本作为
StringIO
对象,从而允许Python关闭该文件。
我建议使用更简单的
merger.append(file(filename,'rb'))
,因为其他人报告说
PdfFileReader
对象可能会保留在内存中,即使调用了
writer.close()
。
希望这有所帮助!
编辑: 我假设您正在使用
PyPDF2
,而不是
PyPDF
。 如果您不是,则强烈建议您进行切换,因为PyPDF已不再得到维护,作者正式批准Phaseit开发PyPDF2。
如果由于某些原因您无法切换到PyPDF2(许可证,系统限制等),则
PdfFileMerger
将无法使用。 在这种情况下,您可以重用PyPDF2的
merge
函数中的代码(如上所述)将文件的副本创建为
StringIO
对象,并在您的代码中使用它来代替文件对象。
编辑 2: 基于评论
(感谢@Agostino),之前推荐使用
merger.append(PdfFileReader(file(filename, 'rb')))
的建议已更改。
writer.append(PdfFileReader(file(filename, 'rb')))
创建中间PdfFileReader
对象时,无法删除某些文件。即使调用writer.close()
后,它们仍然被锁定。而使用更简单的merger.append(file(filename, 'rb'))
则似乎没有同样的问题。 - Agostinoopen
而不是file
在merger.append(PdfFileReader(file(filename, 'rb')))
. 就像这样merger.append(PdfFileReader(open(filename, 'rb')))
. - Hiebs915