[Python Home] [PEP Index] [PEP Source] |
PEP: | XXX |
---|---|
Title: | Construct for writing statements in top-down order. |
Version: | $Revision: 32 $ |
Last-Modified: | $Date: 2006-04-12 02:30:20 -0700 (Wed, 12 Apr 2006) $ |
Author: | Beni Cherniavsky <cben at users.sf.net>> |
Status: | Draft |
Type: | Standards Track |
Content-Type: | text/x-rst |
Created: | 02-Mar-2003 |
Python-Version: | 2.3 |
Post-History: | 02-Mar-2003 |
This PEP proposes a single construct that achieves some of the benefits of embedding statements inside expressions without actually doing so - it simply allows to write statements in different order, communicating value through variable bindings. This allows it to scale to almost any scenario when one would wish for the power of statements inside expressions, unlike all the proposals to add yet another expression kind. However, it specifically does not aim for brevity.
There is constant pressure to enhance Python expressions with powers that currently only statements have (control structures, variable binding, etc.). The most infamous example would be PEP 308 <wink>. All cases where this would be desirable can be expressed in Python today by computing some intermediate values or defining some local functions before the statement where they get used. The main reasons why this is not entirely satisfactory are brevity (which is not addressed by this proposal) and awkward order.
Agruably, when a value is used in one place only, the optimal reading order is a top-down one, where one sees how it will be used before seeing how it is computed. That's why prefix/infix expressions are more pleasant to read than postfix notation (Forth people excuse me). This is especially true in cases when the value's computation is longer than it's use. Quoting Larry Wall in a perl apocalypse 5:
Poor end-weight design
In linguistics, the notion of end-weight is the idea that people tend to prefer sentences where the short things come first and the long things come last. That minimizes the amount of stuff you have to remember while you're reading or listening.
This leads me to believe that if the computation of the intermediate values could be written after the statement using them, with some indication that they "belong" under this statement, readability would be greatly improved.
The following examples are more or less typical situations where this could be handy. If they are not obvious even before reading the specification, something is wrong with this proposal.
A static method:
meth = staticmethod(meth) given: def meth(*args): do_something(*args)
A property, showing that this allows more than one binding:
x = property(get, set, delete) given: def get(self): return self._x def set(self, val): self._x = val def delete(self): del self._x
That's how using variable to communicate the intermediate values allows this construct to scale to complex scenarios.
Another use for local functions: a construct like lisp's let, useful e.g. for destructuring optional/keyword arguments and seeding recursion:
f(*args, **kwargs) given: def f(a, b=17, c='foo'): do_something(a, b, c) g(1, 2) given: def g
A local class (nobody asked for this but we can do it ;-):
x = C() given: class C: foo = 'bar'
"Conditional operator", used once:
do_something(x231) given: if x % 2 == 0: x231 = x // 2 else: x231 = x * 3 + 1
Note that it is not as short as ?: would be... To use it repeatedly (e.g. in a list comprehension), one would have to put it inside a function (see first example).
Loop comprehension (note the break, can't be done in list comprehension):
do_something(values) given: values = [] for v in vals: if v == SENTINEL: break values.append(v * 3)
Try-except/finally "inside" an expression:
do_something(value) given: try: try: value = foo() except: value = bar() finally: release_foo()
An import that is not important to the understanding:
foo(bar()) given: from quux import foo, bar
Note that these constructs quickly lose their appeal if the do_something lines become over-complicated. One of the most suitable uses is when this line is the return statement of a function.
A new kind of compound statement is introduced:
given_stmt ::= simple_stmt "given" ":" suite
"given" here is a new keyword. The meaning is to execute the suite and afterwards to execute the simple_stmt, so that bindings done in the suite would be availiable in it. The simplest thing is to make this completely equivallent to just writing them one after the other, e.g.:
suite simple_stmt
More complex scoping schemes and the reasons are discussed below.
The basic syntax for this is pretty obvious, given the ideas in the motivation section. I considered using punctuation instead of a keyword but the meaning would then be completely non-obvious to somebody who has never seen it. Since this construct has few parallels in other languages (see prior art below), this would be too bad.
Keyword alternatives: where, with, given, for. The last has the benefit of not being a new keyword but doesn't feel like it belongs here. given is the only one I found that strongly suggests that the suite is executed before the simple statement (because it's in the past tense) - that's why chosen it as the leading option. Feedback and suggestions from the community are particualrly needed on this point.
A new keyword is introduced (unless for is hijacked), so the __future__ game will have to be played. Perhaps it could be done as a pseudo-keyword but that sounds hairy...
This document has been placed in the public domain.