Pillow中的文件处理

以图像形式打开文件时, Pillow 需要文件名, pathlib.Path 对象,或类似文件的对象。 Pillow 使用文件名或 Path 要打开一个文件,所以在本文的其余部分中,它们都将被视为一个类似文件的对象。

前四项是等效的,后一项是危险的,可能会失败:

from PIL import Image
import io
import pathlib

im = Image.open('test.jpg')

im2 = Image.open(pathlib.Path('test.jpg'))

f = open('test.jpg', 'rb')
im3 = Image.open(f)

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

# Dangerous FAIL:
with open('test.jpg', 'rb') as f:
    im5 = Image.open(f)
im5.load() # FAILS, closed file

如果将文件名或类似路径的对象传递到 Pillow ,则 Pillow 打开的结果文件对象也可以在 Image.Image.load() 如果关联的图像没有多个帧,则调用方法。

Pillow一般不能关闭和重新打开文件,因此任何访问该文件需要在关闭之前。

问题

  • 使用“文件上下文管理器”向Pillow提供类似文件的对象是危险的,除非图像的上下文仅限于文件的上下文。

图像生命周期

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

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

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

  • Image.Image.close() 关闭文件并销毁核心图像对象。这在 Pillow 上下文管理器支持中使用。例如。::

    with Image.open('test.jpg') as img:
       ...  # image operations here.
    

单帧图像的生命周期相对简单。文件必须保持打开状态,直到 load()close() 调用函数。

多帧图像更复杂。这个 load() 方法不是终端方法,因此不应关闭基础文件。一般来说,在调用者明确关闭图像之前, Pillow 不知道是否会有其他数据请求。

难题

  • TiffImagePlugin 有一些代码可以将底层文件描述符传递到libtiff中(如果处理的是实际文件)。由于libtiff在内部关闭文件描述符,因此在将其传递到libtiff之前,它是重复的。

  • 我不认为有任何方法可以在不改变懒惰装载的情况下保证安全:

    # Dangerous FAIL:
    with open('test.jpg', 'rb') as f:
        im5 = Image.open(f)
    im5.load() # FAILS, closed file
    

建议的文件处理

  • Image.Image.load() 应该关闭图像文件,除非有多个帧。

  • Image.Image.seek() 不应关闭图像文件。

  • 类库的用户应该 Image.Image.close() 在任何多帧图像上,以确保底层文件已关闭。