LoginSignup
3
2

More than 3 years have passed since last update.

PdfFileReaderを使うときはファイルストリームを閉じてはいけない

Last updated at Posted at 2019-09-16

表題の通りでちょっとハマったのでメモ。

PDFファイルを読み込み、それを分割して保存する場合、PyPDF2というモジュールが便利です。

PyPDF2には、PdfFileReaderPdfFileWriterというクラスがあり、Readerで読み込み、Writerで書き込みと、完全に役割が分かれています。

  r = PdfFileReader(...)
  w = PdfFileWriter()
  w.addPage(r.getPages(0))
  w.addPage(r.getPages(1))
  w.addPage(r.getPages(2))
  # …

クラスでラッピングするときの注意

で、PDFファイルの読み込み処理にいろんな処理を挟むときなど、ついついPdfFileReaderをオリジナルのクラスでラッピングしたくなりますが…。

  with open(filename, mode="rb") as f:
    self.__reader = PdfFileReader(f, strict=False)
    # 色々な処理

よくあるプログラムのように、このように書いてしまうと、withを抜けた後にPdfFileWriterでPDFに書き込みを行うことができません(エラーにはならないですが、出力されるページがすべて真っ白になります)。

PdfFileReaderは読み込みの時に、ファイルをすべて読み込まず、必要な時に適宜ファイルを読み込む実装になっているため、ファイルストリームは開きっぱなしにしておかなければいけません(よく見ると、いちおうPdfFileReaderのドキュメントコメントにそれっぽいことが書いてあります)。

ですので、クラスでラッピングする場合も

  self.__fs = open(pdf_filename, mode="rb")
  reader = self.__reader = PdfFileReader(self.__fs, strict=False)
  # 色々な処理

とし、別途self.__fs.close()を呼ぶ必要があります。他のサンプルを見てもなぜかwith open("PDF"...) as ... のような書き方をしていないなあ と思ったら。

クラスをwith文対応にする

せっかくなのでラッピングしたクラスもwith文に対応させておくと後々使いやすいです。これまたいつもと同じですが。

  def __enter__(self):
    return self

  def __exit__(self, exc_type, exc_value, tb):
    if exc_type is not None:
      import traceback
      traceback.print_exception(exc_type, exc_value, tb)
    self.close()
    return self

という定義をクラスのどこかに加えればOKです。__enter__は「もし__init__に引数があったとしても」そのままselfreturnすればOKです。

また、サンプルコードによっては__exit__の引数がselfだけになっている場合があるのでこちらも注意です(引数が足りないとwith文を抜けたときにエラーが発生します)。

参考資料

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2