PEP: 310 Title: Reliable Acquisition/Release Pairs Version: $Revision$
Last-Modified: $Date$ Author: Michael Hudson <mwh@python.net>, Paul
Moore <p.f.moore@gmail.com> Status: Rejected Type: Standards Track
Content-Type: text/x-rst 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.

Pronouncement

This PEP is rejected in favor of PEP 343.

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__()

(The presence of an __exit__ method is not checked like that of
__enter__ to ensure that using inappropriate objects in with: statements
gives an 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 similarities 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.

Copyright

This document has been placed in the public domain.

[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
https://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 https://mail.python.org/pipermail/python-dev/2003-August/037803.html