Merge pull request #551 from wiredfool/with

Image.close, Context manager support
This commit is contained in:
wiredfool 2014-04-17 22:20:30 -07:00
commit 5ca5652d0b
6 changed files with 59 additions and 12 deletions

View File

@ -92,7 +92,7 @@ except ImportError:
from PIL import ImageMode from PIL import ImageMode
from PIL._binary import i8, o8 from PIL._binary import i8, o8
from PIL._util import isPath, isStringType from PIL._util import isPath, isStringType, deferred_error
import os, sys import os, sys
@ -497,6 +497,35 @@ class Image:
_makeself = _new # compatibility _makeself = _new # compatibility
# Context Manager Support
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def close(self):
"""
Closes the file pointer, if possible.
This operation will destroy the image core and release it's memory.
The image data will be unusable afterward.
This function is only required to close images that have not
had their file read and closed by the
:py:meth:`~PIL.Image.Image.load` method.
"""
try:
self.fp.close()
except Exception as msg:
if Image.DEBUG:
print ("Error closing: %s" %msg)
# Instead of simply setting to None, we're setting up a
# deferred error that will better explain that the core image
# object is gone.
self.im = deferred_error(ValueError("Operation on closed image"))
def _copy(self): def _copy(self):
self.load() self.load()
self.im = self.im.copy() self.im = self.im.copy()
@ -642,7 +671,8 @@ class Image:
Allocates storage for the image and loads the pixel data. In Allocates storage for the image and loads the pixel data. In
normal cases, you don't need to call this method, since the normal cases, you don't need to call this method, since the
Image class automatically loads an opened image when it is Image class automatically loads an opened image when it is
accessed for the first time. accessed for the first time. This method will close the file
associated with the image.
:returns: An image access object. :returns: An image access object.
""" """
@ -2074,10 +2104,11 @@ def open(fp, mode="r"):
""" """
Opens and identifies the given image file. Opens and identifies the given image file.
This is a lazy operation; this function identifies the file, but the This is a lazy operation; this function identifies the file, but
actual image data is not read from the file until you try to process the file remains open and the actual image data is not read from
the data (or call the :py:meth:`~PIL.Image.Image.load` method). the file until you try to process the data (or call the
See :py:func:`~PIL.Image.new`. :py:meth:`~PIL.Image.Image.load` method). See
:py:func:`~PIL.Image.new`.
:param file: A filename (string) or a file object. The file object :param file: A filename (string) or a file object. The file object
must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and

View File

@ -89,8 +89,8 @@ try:
except ImportError as ex: except ImportError as ex:
# Allow error import for doc purposes, but error out when accessing # Allow error import for doc purposes, but error out when accessing
# anything in core. # anything in core.
from _util import import_err from _util import deferred_error
_imagingcms = import_err(ex) _imagingcms = deferred_error(ex)
from PIL._util import isStringType from PIL._util import isStringType
core = _imagingcms core = _imagingcms

View File

@ -15,7 +15,7 @@ else:
def isDirectory(f): def isDirectory(f):
return isPath(f) and os.path.isdir(f) return isPath(f) and os.path.isdir(f)
class import_err(object): class deferred_error(object):
def __init__(self, ex): def __init__(self, ex):
self.ex = ex self.ex = ex
def __getattr__(self, elt): def __getattr__(self, elt):

View File

@ -2,6 +2,8 @@ from tester import *
from PIL import Image from PIL import Image
import os
def test_sanity(): def test_sanity():
im = lena() im = lena()
@ -9,3 +11,17 @@ def test_sanity():
pix = im.load() pix = im.load()
assert_equal(pix[0, 0], (223, 162, 133)) assert_equal(pix[0, 0], (223, 162, 133))
def test_close():
im = Image.open("Images/lena.gif")
assert_no_exception(lambda: im.close())
assert_exception(ValueError, lambda: im.load())
assert_exception(ValueError, lambda: im.getpixel((0,0)))
def test_contextmanager():
fn = None
with Image.open("Images/lena.gif") as im:
fn = im.fp.fileno()
assert_no_exception(lambda: os.fstat(fn))
assert_exception(OSError, lambda: os.fstat(fn))

View File

@ -126,8 +126,8 @@ Identify Image Files
for infile in sys.argv[1:]: for infile in sys.argv[1:]:
try: try:
im = Image.open(infile) with Image.open(infile) as im:
print(infile, im.format, "%dx%d" % im.size, im.mode) print(infile, im.format, "%dx%d" % im.size, im.mode)
except IOError: except IOError:
pass pass

View File

@ -136,9 +136,9 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space:
.. automethod:: PIL.Image.Image.verify .. automethod:: PIL.Image.Image.verify
.. automethod:: PIL.Image.Image.fromstring .. automethod:: PIL.Image.Image.fromstring
.. deprecated:: 2.0
.. automethod:: PIL.Image.Image.load .. automethod:: PIL.Image.Image.load
.. automethod:: PIL.Image.Image.close
Attributes Attributes
---------- ----------