Pillow中的文件处理#

以图像形式打开文件时,枕头需要文件名, pathlib.Path 对象,或类似文件的对象。枕头使用文件名或 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一般不能关闭和重新打开文件,因此任何访问该文件需要在关闭之前。

图像生命周期#

  • Image.open() 文件名和 Path 对象作为文件打开。从打开的文件中读取元数据。文件保持打开状态以供进一步使用。

  • Image.Image.load() 当需要图像中的像素数据时, load() 被称为。当前帧被读取到内存中。现在可以独立于基础图像文件使用图像。

    任何基于图像实例创建新图像的Pillow方法都将在内部调用 load() 然后读取原始图像上的数据。新图像实例将不会与原始图像文件相关联。

    如果文件名或 Path 对象已传递给 Image.open() ,然后文件对象被枕头打开,被认为是枕头专用的。因此,如果图像是单帧图像,则在读取帧后,该文件将在此方法中关闭。如果图像是多帧图像(如多页TIFF和动画GIF),图像文件将保持打开状态,以便 Image.Image.seek() 可以加载适当的框架。

  • Image.Image.close() 关闭文件并销毁核心图像对象。

    枕头上下文管理器也将关闭该文件,但不会销毁核心图像对象。例如:

    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() 不应关闭图像文件。

  • 库的用户应使用上下文管理器或调用 Image.Image.close() 在使用文件名或 Path 对象以确保关闭基础文件。