240 lines
8.0 KiB
Plaintext
240 lines
8.0 KiB
Plaintext
PEP: 310
|
||
Title: Reliable Acquisition/Release Pairs
|
||
Version: $Revision$
|
||
Last-Modified: $Date$
|
||
Author: Michael Hudson <mwh@python.net>,
|
||
Paul Moore <gustav@morpheus.demon.co.uk>
|
||
Status: Draft
|
||
Type: Standards Track
|
||
Content-Type: text/plain
|
||
Created: 18-Dec-2002
|
||
Python-Version: 2.4
|
||
Post-History:
|
||
|
||
|
||
Abstract
|
||
|
||
It would be nice to have a less typing-intense way of writing:
|
||
|
||
the_lock.acquire()
|
||
try:
|
||
....
|
||
finally:
|
||
the_lock.release()
|
||
|
||
This PEP proposes a piece of syntax (a 'with' block) and a
|
||
"small-i" interface that generalizes the above.
|
||
|
||
|
||
Rationale
|
||
|
||
One of the advantages of Python's exception handling philosophy is
|
||
that it makes it harder to do the "wrong" thing (e.g. failing to
|
||
check the return value of some system call). Currently, this does
|
||
not apply to resource cleanup. The current syntax for acquisition
|
||
and release of a resource (for example, a lock) is
|
||
|
||
the_lock.acquire()
|
||
try:
|
||
....
|
||
finally:
|
||
the_lock.release()
|
||
|
||
This syntax separates the acquisition and release by a (possibly
|
||
large) block of code, which makes it difficult to confirm "at a
|
||
glance" that the code manages the resource correctly. Another
|
||
common error is to code the "acquire" call within the try block,
|
||
which incorrectly releases the lock if the acquire fails.
|
||
|
||
|
||
Basic Syntax and Semantics
|
||
|
||
The syntax of a 'with' statement is as follows::
|
||
|
||
'with' [ var '=' ] expr ':'
|
||
suite
|
||
|
||
This statement is defined as being equivalent to the following
|
||
sequence of statements:
|
||
|
||
var = expr
|
||
|
||
if hasattr(var, "__enter__"):
|
||
var.__enter__()
|
||
|
||
try:
|
||
suite
|
||
|
||
finally:
|
||
var.__exit__()
|
||
|
||
Note that this makes using an object that does not have an
|
||
__exit__() method a fail-fast error.
|
||
|
||
If the variable is omitted, an unnamed object is allocated on the
|
||
stack. In that case, the suite has no access to the unnamed object.
|
||
|
||
|
||
Possible Extensions
|
||
|
||
A number of potential extensions to the basic syntax have been
|
||
discussed on the Python Developers list. None of these extensions
|
||
are included in the solution proposed by this PEP. In many cases,
|
||
the arguments are nearly equally strong in both directions. In
|
||
such cases, the PEP has always chosen simplicity, simply because
|
||
where extra power is needed, the existing try block is available.
|
||
|
||
Multiple expressions
|
||
|
||
One proposal was for allowing multiple expressions within one
|
||
'with' statement. The __enter__ methods would be called left to
|
||
right, and the __exit__ methods right to left. The advantage of
|
||
doing so is that where more than one resource is being managed,
|
||
nested 'with' statements will result in code drifting towards the
|
||
right margin. The solution to this problem is the same as for any
|
||
other deep nesting - factor out some of the code into a separate
|
||
function. Furthermore, the question of what happens if one of the
|
||
__exit__ methods raises an exception (should the other __exit__
|
||
methods be called?) needs to be addressed.
|
||
|
||
Exception handling
|
||
|
||
An extension to the protocol to include an optional __except__
|
||
handler, which is called when an exception is raised, and which
|
||
can handle or re-raise the exception, has been suggested. It is
|
||
not at all clear that the semantics of this extension can be made
|
||
precise and understandable. For example, should the equivalent
|
||
code be try ... except ... else if an exception handler is
|
||
defined, and try ... finally if not? How can this be determined
|
||
at compile time, in general? The alternative is to define the
|
||
code as expanding to a try ... except inside a try ... finally.
|
||
But this may not do the right thing in real life.
|
||
|
||
The only use case identified for exception handling is with
|
||
transactional processing (commit on a clean finish, and rollback
|
||
on an exception). This is probably just as easy to handle with a
|
||
conventional try ... except ... else block, and so the PEP does
|
||
not include any support for exception handlers.
|
||
|
||
|
||
Implementation Notes
|
||
|
||
There is a potential race condition in the code specified as
|
||
equivalent to the with statement. For example, if a
|
||
KeyboardInterrupt exception is raised between the completion of
|
||
the __enter__ method call and the start of the try block, the
|
||
__exit__ method will not be called. This can lead to resource
|
||
leaks, or to deadlocks. [XXX Guido has stated that he cares about
|
||
this sort of race condition, and intends to write some C magic to
|
||
handle them. The implementation of the 'with' statement should
|
||
copy this.]
|
||
|
||
|
||
Open Issues
|
||
|
||
Should existing classes (for example, file-like objects and locks)
|
||
gain appropriate __enter__ and __exit__ methods? The obvious
|
||
reason in favour is convenience (no adapter needed). The argument
|
||
against is that if built-in files have this but (say) StringIO
|
||
does not, then code that uses "with" on a file object can't be
|
||
reused with a StringIO object. So __exit__ = close becomes a part
|
||
of the "file-like object" protocol, which user-defined classes may
|
||
need to support.
|
||
|
||
The __enter__ hook may be unnecessary - for many use cases, an
|
||
adapter class is needed and in that case, the work done by the
|
||
__enter__ hook can just as easily be done in the __init__ hook.
|
||
|
||
If a way of controlling object lifetimes explicitly was available,
|
||
the function of the __exit__ hook could be taken over by the
|
||
existing __del__ hook. An email exchange[1] with a proponent of
|
||
this approach left one of the authors even more convinced that
|
||
it isn't the right idea...
|
||
|
||
It has been suggested[2] that the "__exit__" method be called
|
||
"close", or that a "close" method should be considered if no
|
||
__exit__ method is found, to increase the "out-of-the-box utility"
|
||
of the "with ..." construct.
|
||
|
||
There are some simiralities in concept between 'with ...' blocks
|
||
and generators, which have led to proposals that for loops could
|
||
implement the with block functionality[3]. While neat on some
|
||
levels, we think that for loops should stick to being loops.
|
||
|
||
|
||
Alternative Ideas
|
||
|
||
IEXEC: Holger Krekel -- generalised approach with XML-like syntax
|
||
(no URL found...)
|
||
|
||
Holger has much more far-reaching ideas about "execution monitors"
|
||
that are informed about details of control flow in the monitored
|
||
block. While interesting, these ideas could change the language
|
||
in deep and subtle ways and as such belong to a different PEP.
|
||
|
||
Any Smalltalk/Ruby anonymous block style extension obviously
|
||
subsumes this one.
|
||
|
||
PEP 319 is in the same area, but did not win support when aired on
|
||
python-dev.
|
||
|
||
|
||
Backwards Compatibility
|
||
|
||
This PEP proposes a new keyword, so the __future__ game will need
|
||
to be played.
|
||
|
||
|
||
Cost of Adoption
|
||
|
||
Those who claim the language is getting larger and more
|
||
complicated have something else to complain about. It's something
|
||
else to teach.
|
||
|
||
For the proposal to be useful, many file-like and lock-like
|
||
classes in the standard library and other code will have to have
|
||
|
||
__exit__ = close
|
||
|
||
or similar added.
|
||
|
||
|
||
Cost of Non-Adoption
|
||
|
||
Writing correct code continues to be more effort than writing
|
||
incorrect code.
|
||
|
||
|
||
References
|
||
|
||
There are various python-list and python-dev discussions that
|
||
could be mentioned here.
|
||
|
||
[1] Off-list conversation between Michael Hudson and Bill Soudan
|
||
(made public with permission)
|
||
http://starship.python.net/crew/mwh/pep310/
|
||
|
||
[2] Samuele Pedroni on python-dev
|
||
http://mail.python.org/pipermail/python-dev/2003-August/037795.html
|
||
|
||
[3] Thread on python-dev with subject
|
||
|
||
[Python-Dev] pre-PEP: Resource-Release Support for Generators
|
||
|
||
starting at
|
||
|
||
http://mail.python.org/pipermail/python-dev/2003-August/037803.html
|
||
|
||
Copyright
|
||
|
||
This document has been placed in the public domain.
|
||
|
||
|
||
|
||
Local Variables:
|
||
mode: indented-text
|
||
indent-tabs-mode: nil
|
||
sentence-end-double-space: t
|
||
fill-column: 70
|
||
End:
|