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()
,以确保底层文件被关闭。