Pillowでのファイル処理

Pillowでファイルを画像として開く場合、Pillowはファイル名、os.PathLikeオブジェクト、またはファイルライクオブジェクトを必要とします。Pillowはファイル名またはPathを使用してファイルを開くため、この記事の残りの部分では、これらはすべてファイルライクオブジェクトとして扱われます。

以下はすべて同等です

from PIL import Image
import io
import pathlib

with Image.open("test.jpg") as im:
    ...

with Image.open(pathlib.Path("test.jpg")) as im2:
    ...

with open("test.jpg", "rb") as f:
    im3 = Image.open(f)
    ...

with open("test.jpg", "rb") as f:
    im4 = Image.open(io.BytesIO(f.read()))
    ...

ファイル名またはパスライクオブジェクトがPillowに渡された場合、Pillowによって開かれた結果のファイルオブジェクトは、関連する画像に複数のフレームがない場合、Image.Image.load()メソッドが呼び出された後でPillowによって閉じられることもあります。

Pillowは一般にファイルを閉じて再度開くことができないため、そのファイルへのアクセスはすべて、閉じる前に行う必要があります。

画像のライフサイクル

  • Image.open() ファイル名とPathオブジェクトはファイルとして開かれます。メタデータは、開いているファイルから読み取られます。ファイルは、さらなる使用のために開いたままにされます。

  • Image.Image.load() 画像のピクセルデータが必要な場合、load()が呼び出されます。現在のフレームがメモリに読み込まれます。画像は、基になる画像ファイルとは独立して使用できるようになります。

    別の画像に基づいて新しい画像インスタンスを作成するPillowメソッドは、内部的に元の画像でload()を呼び出し、データを読み取ります。新しい画像インスタンスは、元の画像ファイルに関連付けられません。

    ファイル名またはPathオブジェクトがImage.open()に渡された場合、ファイルオブジェクトはPillowによって開かれ、Pillowによって排他的に使用されると見なされます。したがって、画像がシングルフレーム画像である場合、フレームが読み取られた後、このメソッドでファイルが閉じられます。画像がマルチフレーム画像(例:マルチページTIFFおよびアニメーションGIF)の場合、Image.Image.seek()が適切なフレームをロードできるように、画像ファイルは開いたままになります。

  • Image.Image.close() ファイルを閉じ、コア画像オブジェクトを破棄します。

    Pillowコンテキストマネージャーもファイルを閉じますが、コア画像オブジェクトは破棄しません。例:

    with Image.open("test.jpg") as img:
        img.load()
    assert img.fp is None
    img.save("test.png")
    

シングルフレーム画像のライフサイクルは比較的単純です。ファイルは、load()またはclose()関数が呼び出されるか、コンテキストマネージャーが終了するまで開いたままにする必要があります。

マルチフレーム画像はより複雑です。load()メソッドはターミナルメソッドではないため、基になるファイルを閉じるべきではありません。一般的に、Pillowは、呼び出し元が画像を明示的に閉じるまで、追加のデータのリクエストがあるかどうかを認識しません。

複雑さ

  • TiffImagePluginには、基になるファイル記述子をlibtiffに渡すコードがあります(実際のファイルで作業している場合)。libtiffはファイル記述子を内部的に閉じるため、libtiffに渡す前に複製されます。

  • ファイルが閉じられた後、ファイルアクセスを必要とする操作は失敗します

    with open("test.jpg", "rb") as f:
        im5 = Image.open(f)
    im5.load()  # FAILS, closed file
    
    with Image.open("test.jpg") as im6:
        pass
    im6.load()  # FAILS, closed file
    

提案されたファイル処理

  • Image.Image.load()は、複数のフレームがない限り、画像ファイルを閉じる必要があります。

  • Image.Image.seek()は、画像ファイルを決して閉じるべきではありません。

  • ライブラリのユーザーは、コンテキストマネージャーを使用するか、ファイル名またはPathオブジェクトで開いた任意の画像でImage.Image.close()を呼び出して、基になるファイルが閉じられていることを確認する必要があります。