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