Gần đây tôi đi qua cùng một vấn đề này chính xác, vì vậy tôi đào vào PyPDF2 để xem những gì đang xảy ra, và làm thế nào để giải quyết nó.
Lưu ý: Tôi giả định rằng filename
là một chuỗi đường dẫn tệp được định dạng tốt. Giả sử như nhau cho tất cả các mã của tôi
The Short trả lời
Sử dụng lớp PdfFileMerger()
thay vì lớp PdfFileWriter()
. Tôi đã cố gắng để cung cấp những điều sau đây giống như chặt chẽ nội dung của bạn như tôi có thể:
from PyPDF2 import PdfFileMerger, PdfFileReader
[...]
merger = PdfFileMerger()
for filename in filenames:
merger.append(PdfFileReader(file(filename, 'rb')))
merger.write("document-output.pdf")
The Long trả lời
Cách bạn đang sử dụng PdfFileReader
và PdfFileWriter
là giữ mỗi tập tin mở, và cuối cùng khiến Python tạo IOError 24. Để cụ thể hơn, khi bạn thêm một trang vào PdfFileWriter
, bạn sẽ thêm tham chiếu đến trang trong PdfFileReader
mở (do đó đã xảy ra lỗi IO được ghi chú nếu bạn đóng tệp). Python phát hiện tệp vẫn được tham chiếu và không thực hiện bất kỳ việc thu thập rác/tệp tự động nào mặc dù sử dụng lại tệp xử lý. Chúng vẫn mở cho đến khi PdfFileWriter
không còn cần quyền truy cập vào chúng, tại số output.write(outputStream)
trong mã của bạn.
Để giải quyết vấn đề này, hãy tạo bản sao trong bộ nhớ của nội dung và cho phép tệp được đóng. Tôi nhận thấy trong cuộc phiêu lưu của tôi thông qua mã PyPDF2 rằng lớp PdfFileMerger()
đã có chức năng này, vì vậy thay vì tái phát minh ra bánh xe, tôi đã chọn sử dụng nó thay thế. Tuy nhiên, tôi đã học được rằng cái nhìn ban đầu của tôi tại PdfFileMerger
không đủ gần và nó chỉ tạo ra các bản sao trong một số điều kiện nhất định.
nỗ lực ban đầu của tôi trông giống như sau đây, và được kết quả trong cùng một vấn đề IO:
merger = PdfFileMerger()
for filename in filenames:
merger.append(filename)
merger.write(output_file_path)
Nhìn vào mã nguồn PyPDF2, chúng ta thấy rằng append()
đòi hỏi fileobj
để được thông qua, và sau đó sử dụng merge()
, chuyển đến trang cuối cùng của nó dưới dạng vị trí tệp mới. merge()
nào sau đây với fileobj
(trước khi mở nó với PdfFileReader(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
Chúng ta có thể thấy rằng tùy chọn append()
không chấp nhận một chuỗi, và khi làm như vậy, giả định đó là một đường dẫn tập tin và tạo ra một đối tượng tập tin tại vị trí đó Kết quả cuối cùng là chính xác những gì chúng tôi đang cố gắng tránh.Một đối tượng PdfFileReader()
giữ mở một tệp cho đến khi tệp được ghi cuối cùng!
Tuy nhiên, nếu chúng ta tạo đối tượng tệp của chuỗi đường dẫn tệp hoặc a PdfFileReader
(xem Chỉnh sửa 2) đối tượng của chuỗi đường dẫn trước nó được chuyển vào append()
, nó sẽ tự động tạo bản sao cho chúng tôi làm đối tượng StringIO
, cho phép Python đóng tệp.
Tôi khuyên bạn nên đơn giản hơn merger.append(file(filename, 'rb'))
, vì những người khác đã báo cáo rằng đối tượng PdfFileReader
có thể vẫn mở trong bộ nhớ, ngay cả sau khi gọi writer.close()
.
Hy vọng điều này giúp!
EDIT: Tôi cho rằng bạn đang sử dụng PyPDF2
, chứ không phải PyPDF
. Nếu bạn không, tôi khuyên bạn nên chuyển đổi, như PyPDF không còn được duy trì với các tác giả cho phước lành chính thức của mình để Phaseit trong việc phát triển PyPDF2.
Nếu vì lý do nào đó bạn không thể đổi sang PyPDF2 (cấp phép, hạn chế hệ thống, v.v.) hơn PdfFileMerger
sẽ không khả dụng cho bạn. Trong tình huống đó, bạn có thể sử dụng lại mã từ chức năng merge
của PyPDF2 (được cung cấp ở trên) để tạo một bản sao của tệp dưới dạng đối tượng StringIO
và sử dụng nó trong mã của bạn thay cho đối tượng tệp.
EDIT 2: giới thiệu trước của việc sử dụng merger.append(PdfFileReader(file(filename, 'rb')))
về thay đổi dựa trên ý kiến (Cảm ơn @Agostino).
Tôi sẽ thành thật; Tôi chưa đọc câu trả lời dài. Câu trả lời ngắn là tuyệt vời mặc dù. – BeReal82
Tôi nhận thấy rằng tôi không thể xóa một số tệp được thêm vào tạo đối tượng 'PdfFileReader' trung gian với lệnh 'writer.append (PdfFileReader (tệp (tên tệp' rb ')))'. Chúng vẫn bị khóa ngay cả sau khi gọi 'writer.close()'. Các cuộc gọi đơn giản 'merger.append (tập tin (tên tập tin, 'rb'))' dường như không có cùng một vấn đề. – Agostino
Điều này sẽ không xảy ra với vấn đề bộ nhớ nếu các tệp quá lớn không? – Nishant