Reliable Acquisition/Release Pairs, by Michael Hudson and Paul Moore
This commit is contained in:
parent
d49ecf85a2
commit
a7cd5f569e
|
@ -0,0 +1,194 @@
|
||||||
|
PEP: 310
|
||||||
|
Title: Syntax for 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:
|
||||||
|
if hasattr(var, "__exit__"):
|
||||||
|
var.__exit__()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
The optional assignment in
|
||||||
|
|
||||||
|
'with' [ var '=' ] expr ':'
|
||||||
|
|
||||||
|
was initially considered to be too hard to parse correctly.
|
||||||
|
However, by parsing the statement as
|
||||||
|
|
||||||
|
'with' expr [ '=' expr ] ':'
|
||||||
|
|
||||||
|
and interpreting the result in the compiler phase, this can be
|
||||||
|
worked around.
|
||||||
|
|
||||||
|
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. Unfortunately, no workable proposal for
|
||||||
|
controlling object lifetimes has been made so far.
|
||||||
|
|
||||||
|
|
||||||
|
Alternative Ideas
|
||||||
|
|
||||||
|
IEXEC: Holger Krekel -- generalised approach with XML-like syntax
|
||||||
|
(no URL found...)
|
||||||
|
|
||||||
|
|
||||||
|
Backwards Compatibility
|
||||||
|
|
||||||
|
This PEP proposes a new keyword, so the __future__ game will need
|
||||||
|
to be played.
|
||||||
|
|
||||||
|
|
||||||
|
References
|
||||||
|
|
||||||
|
There are various python-list and python-dev discussions that
|
||||||
|
could be mentioned here.
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
Loading…
Reference in New Issue