[Python-Dev] A Hygienic Macro System in Python?

Barry A. Warsaw barry@zope.com
Tue, 19 Mar 2002 21:37:08 -0500


>>>>> "TP" == Tim Peters <tim.one@comcast.net> writes:

    |     It would be cool, IMO, if people could define their own
    |     block statements in Python.

    TP> I think all the use cases you had in mind fit this too, and
    TP> it's not vacuous.

Indeed.  Here's a strawman pure-Python implementation based on Bernard
Herzog's idea.  I'm not sure that the semantics of using() are quite
right (mine are different than Bernard's), but that can all be fleshed
out in the PEP <287 wink>.

Random bizarre thoughts:

- maybe a using: block could be named and passed around
- maybe `break' would be legal in a using: block
- maybe else: could follow a using: block instead of the __else__() method

In the following example, imagine that

    using <expr>:
	suite()

is syntactic sugar for:

    using(<expr>, suite)

seems-neat-and-possibly-even-useful-ly y'rs,
-Barry

-------------------- snip snip --------------------using.py
class IUsing:
    """A poor-man's interface for a using: expression."""
    counter = 0

    def __enter__(self):
        """Called when a using: block is entered."""

    def __leave__(self):
        """Called when a using: block is left, regardless of whether an
        exception has occurred or not.
        """

    def __exception__(self):
        """Called when an exception has occurred during a using: block."""

    def __else__(self):
        """Called when a using: block is exited normally."""

    def __iter__(self):
        """Creates an iterative using: block."""
        return self

    def next(self):
        """Raise StopIteration when an iterative using: block is done."""
        # By default, we do exactly one pass through the loop.
        if not self.counter:
            self.counter += 1
        else:
            raise StopIteration


def using(resource, suite):
    if not isinstance(resource, IUsing):
        raise TypeError, 'first argument must be an IUsing'
    if not callable(suite):
        raise TypeError, 'second argument must be callable'
    resource.__enter__()
    try:
        try:
            for __x__ in resource:
                suite()
        except:
            resource.__exception__()
            raise
        else:
            resource.__else__()
    finally:
        resource.__leave__()


class WithLock(IUsing):
    # Not part of the IUsing interface...
    def __init__(self):
        self.lock = 0

    def __enter__(self):
        print 'acquiring lock'
        self.lock = 1

    def __leave__(self):
        print 'releasing lock'
        self.lock = 0


_counter = 0
def once():
    global _counter
    print _counter
    _counter += 1


# At last!
using(WithLock(), once)
-------------------- snip snip --------------------

% python /tmp/using.py
acquiring lock
0
releasing lock