[Python-ideas] ScopeGuardStatement/Defer Proposal

Nick Coghlan ncoghlan at gmail.com
Sun Feb 19 02:27:12 CET 2012


On Sun, Feb 19, 2012 at 9:57 AM, Nathan Rice
<nathan.alexander.rice at gmail.com> wrote:
> I enjoy writing python a lot, and would prefer to use it rather than
> ruby/lisp/java/etc in most cases. My suggestions come from
> frustrations that occur when using python in areas where the right
> answer is probably just to use a different language.  If I knew that
> what I wanted was at odds with the vision for python, I would have
> less of an issue just accepting circumstances, and would just get to
> work rather than sidetracking discussions on this list.

The core problem comes down to the differences between Guido's
original PEP 340 idea (which was much closer in power to Ruby's
blocks, since it was a new looping construct that allowed 0-or-more
executions of the contained block) and the more constrained with
statement that is defined in PEP 343 (which will either execute the
body once or throw an exception, distinguishing it clearly from both
the looping constructs and if statements).

The principle Guido articulated when making that decision was:
"different forms of flow control should look different at the point of
invocation".

So, where a language like Ruby just defines one protocol (callbacks,
supplemented by anonymous blocks that run directly in the namespace of
the containing function) and uses it for pretty much *all* flow
control (including all their loop constructs), Python works the other
way around, defining *different* protocols for different patterns of
invocation.

This provides a gain in readability on the Python side. When you see
any of the following in Python:

   @whatever()
   def f():
        pass

    with whatever():
        # Do something!

    for x in whatever():
        # Do something!

It places a lot of constraints on the nature of the object returned by
"whatever()" - even without knowing anything else about it, you know
the first must return a decorator, the second a context manager, and
the third an iterable. If that's all you need to know at this point in
time, you don't need to worry about the details - the local syntax
tells you the important things you need to know about the flow
control.

In Ruby, though, all of them (assuming it isn't actually important
that the function name be bound locally) could be written like this:

    whatever() do:
        # Do something!
    end

Is it a context manager? An iterable? Some other kind of callback?
There's nothing in the syntax to tell you that - you're relying on
naming conventions to provide that information (like the ".foreach"
convention for iteration methods). That approach can obviously work
(otherwise Ruby wouldn't be as popular as it is), but it *does* make
it harder to pick up a piece of code and understand the possible
control flows without looking elsewhere.

However, this decision to be explicit about flow control for the
benefit of the *reader* brings with it a high *cost* on the Python
side for the code *author*: where Ruby works by defining a nice syntax
and semantics for callback based programming and building other
language constructs on top of that, Python *doesn't currently have* a
particularly nice general purpose native syntax for callback based
programming.

Decorators do work in many cases (especially simple callback
registration), but they sometimes feel wrong because they're mainly
designed to modify how a function is defined, not implement key
program flow control constructs. However, their flexibility shouldn't
be underestimated, and the CallbackStack API is designed to help
Python developers push decorators and context managers closer to those
limits *without* needing new language constructs. By decoupling the
callback stack from the code layout, it gives you full *programmatic*
control of the kinds of things context managers can help with when you
know in advance exactly what you want to do.

*If* CallbackStack proves genuinely popular (and given the number of
proposals I have seen along these lines, and the feedback I have
received on ContextStack to date, I expect it will), and people start
to develop interesting patterns for using it, *then* we can start
looking at the possibility of dedicated syntax to streamline
particular use cases (just as the with statement itself was designed
to streamline various use cases of the more general try statement).

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia



More information about the Python-ideas mailing list