Updates based on python-ideas discussion

This commit is contained in:
Nick Coghlan 2010-07-20 22:08:46 +00:00
parent d42a5e3168
commit f9ab5fc41e
1 changed files with 161 additions and 52 deletions

View File

@ -1,5 +1,5 @@
PEP: 3150
Title: Statement local namespaces (aka "where" clause)
Title: Statement local namespaces (aka "given" clause)
Version: $Revision$
Last-Modified: $Date$
Author: Nick Coghlan <ncoghlan@gmail.com>
@ -15,7 +15,7 @@ Resolution: TBD
Abstract
========
A recurring proposal on python-ideas is the addition of some form of
A recurring proposal ([1], [2], [3]) on python-ideas is the addition of some form of
statement local namespace.
This PEP is intended to serve as a focal point for those ideas, so we
@ -41,7 +41,7 @@ the proposed semantics to get it moving again.
Proposal
========
This PEP proposes the addition of an optional "where" clause to the
This PEP proposes the addition of an optional ``given`` clause to the
syntax for simple statements which may contain an expression. The
current list of simple statements that would be affected by this
addition is as follows:
@ -55,49 +55,107 @@ addition is as follows:
* raise statement
* assert statement
The ``where`` clause would allow subexpressions to be referenced by
The ``given`` clause would allow subexpressions to be referenced by
name in the header line, with the actual definitions following in
the indented clause. As a simple example::
c = sqrt(a*a + b*b) where:
c = sqrt(a*a + b*b) given:
a = retrieve_a()
b = retrieve_b()
Torture Test
============
Rationale
=========
An implementation of this PEP must support execution of the following
code at module, class and function scope::
Some past language features (specifically function decorators
and list comprehensions) were motivated, at least in part, by
the desire to give the important parts of a statement more
prominence when reading code. In the case of function decorators,
information such as whether or not a method is a class or static
method can now be found in the function definition rather than
after the function body. List comprehensions similarly take the
expression being assigned to each member of the list and move it
to the beginning of the expression rather than leaving it buried
inside a ``for`` loop.
b = {}
a = b[f(a)] = x where:
x = 42
def f(x):
return x
assert "x" not in locals()
assert "f" not in locals()
assert a == 42
assert d[42] == 42 where:
d = b
assert "d" not in locals()
The rationale for the ``given`` clause is similar. Currently,
breaking out a subexpression requires naming that subexpression
*before* the actual statement of interest. The ``given`` clause
is designed to allow a programmer to highlight for the reader
the statement which is actually of interest (and presumably has
significance for later code) while hiding the most likely irrelevant
"calculation details" inside an indented suite.
Most naive implementations will choke on the first complex assignment,
while less naive but still broken implementations will fail when
the torture test is executed at class scope.
Using the simple example from the proposal section, the current Python
equivalent would be::
And yes, that's a perfectly well-defined assignment statement. Insane,
I agree, but legal::
a = retrieve_a()
b = retrieve_b()
c = sqrt(a*a + b*b)
>>> def f(x): return x
...
>>> x = 42
>>> b = {}
>>> a = b[f(a)] = x
>>> a
42
>>> b
{42: 42}
If later code is only interested in the value of c, then the
details involved in retrieving the values of a and b may be an
unnecessary distraction to the reader (particularly if those
details are more complicated than the simple function calls
shown in the example).
To use a more illustrative example (courtesy of Alex Light),
which of the following is easier to comprehend?
Subexpressions up front?::
sea = water()
temp = get_temperature(sea)
depth = get_depth(sea)
purity = get_purity(sea)
saltiness = get_salinity(sea)
size = get_size(sea)
density = get_density(sea)
desired_property = calc_value(temp, depth, purity,
salinity, size, density)
# Further operations using desired_property
Or subexpressions indented?::
desired_property = calc_value(temp, depth, purity,
salinity, size, density) given:
sea = water()
temp = get_temperature(sea)
depth = get_depth(sea)
purity = get_purity(sea)
saltiness = get_salinity(sea)
size = get_size(sea)
density = get_density(sea)
# Further operations using desired_property
The ``given`` clause may also function as a more readable
alternative to some uses of lambda expressions and similar
constructs when passing one-off functions to operations
like ``sorted``.
One way to think of the proposed clause is as a middle
ground between normal in-line code and separation of an
operation out into a dedicated function.
Keyword Choice
==============
This proposal initially used ``where`` based on the name of a similar
construct in Haskell. However, it has been pointed out that there
are existing Python libraries (such as Numpy [4]) that already use
``where`` in the SQL query condition sense, making that keyword choice
potentially confusing.
While ``given`` may also be used as a variable name (and hence would be
deprecated using the usual ``__future__`` dance for introducing
new keywords), it is associated much more strongly with the desired
"here are some extra variables this expression may use" semantics
for the new clause.
Reusing the ``with`` keyword has also been proposed. This has the
advantage of avoiding the addition of a new keyword, but also has
a high potential for confusion as the ``with`` clause and ``with``
statement would look similar but do completely different things.
That way lies C++ and Perl :)
Syntax Change
=============
@ -117,12 +175,12 @@ New::
expr_stmt: testlist_star_expr (augassign (yield_expr|testlist) |
('=' (yield_expr|testlist_star_expr))*) [where_clause]
del_stmt: 'del' exprlist [where_clause]
return_stmt: 'return' [testlist] [where_clause]
yield_stmt: yield_expr [where_clause]
raise_stmt: 'raise' [test ['from' test]] [where_clause]
assert_stmt: 'assert' test [',' test] [where_clause]
where_clause: "where" ":" suite
del_stmt: 'del' exprlist [given_clause]
return_stmt: 'return' [testlist] [given_clause]
yield_stmt: yield_expr [given_clause]
raise_stmt: 'raise' [test ['from' test]] [given_clause]
assert_stmt: 'assert' test [',' test] [given_clause]
given_clause: "given" ":" suite
(Note that expr_stmt in the grammar covers assignment and augmented
assignment in addition to simple expression statements)
@ -145,20 +203,22 @@ before it could be seriously considered. This would likely require a
non-trivial restructuring of the grammar, breaking up small_stmt and
flow_stmt to separate the statements that potentially contain arbitrary
subexpressions and then allowing a single one of those statements with
a ``where`` clause at the simple_stmt level. Something along the lines of::
a ``given`` clause at the simple_stmt level. Something along the lines of::
stmt: simple_stmt | given_stmt | compound_stmt
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
where_stmt: subexpr_stmt (where_clause | (';' small_stmt)* [';']) NEWLINE
subexpr_stmt: expr_stmt | del_stmt | flow_subexpr_stmt | assert_stmt
small_stmt: (pass_stmt | flow_stmt | import_stmt |
global_stmt | nonlocal_stmt)
flow_stmt: break_stmt | continue_stmt
given_stmt: subexpr_stmt (given_clause |
(';' (small_stmt | subexpr_stmt))* [';']) NEWLINE
subexpr_stmt: expr_stmt | del_stmt | flow_subexpr_stmt | assert_stmt
flow_subexpr_stmt: return_stmt | raise_stmt | yield_stmt
where_clause: "where" ":" suite
given_clause: "given" ":" suite
For reference, here are the current definitions at that level::
stmt: simple_stmt | compound_stmt
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt |
import_stmt | global_stmt | nonlocal_stmt | assert_stmt)
@ -177,19 +237,29 @@ Common Objections
jump around a little strangely, as the body of the ``where``
clause is executed before the simple statement in the clause
header. The closest any other part of Python comes to this
before is the out of order evaluation in conditional
expressions.
is the out of order evaluation in list comprehensions,
generator expressions and conditional expressions.
These objections should not be dismissed lightly - the proposal
in this PEP needs to be subjected to the test of application to
a large code base (such as the standard library) in a search
for examples where the readability of real world code is genuinely
enhanced.
New PEP 8 guidelines would also need to be developed to provide
appropriate direction on when to use the ``given`` clause over
ordinary variable assignments.
Possible Additions
==================
* The current proposal allows the addition of a ``where`` clause only
* The current proposal allows the addition of a ``given`` clause only
for simple statements. Extending the idea to allow the use of
compound statements would be quite possible, but doing so raises
serious readability concerns (as values defined in the ``where``
serious readability concerns (as values defined in the ``given``
clause may be used well before they are defined, exactly the kind
of readability trap that decorators were designed to eliminate)
of readability trap that other features like decorators and ``with``
statements are designed to eliminate)
* Currently only the outermost clause of comprehensions and generator
expressions can reference the surrounding namespace when executed
@ -197,7 +267,43 @@ Possible Additions
associated namespace semantics could allow that restriction to be
lifted. There would be backwards compatibility implications in doing
so as existing code may be relying on the behaviour of ignoring
class level variables.
class level variables, but the idea is worth considering.
Torture Test
============
An implementation of this PEP must support execution of the following
code at module, class and function scope::
b = {}
a = b[f(a)] = x given:
x = 42
def f(x):
return x
assert "x" not in locals()
assert "f" not in locals()
assert a == 42
assert d[42] == 42 given:
d = b
assert "d" not in locals()
Most naive implementations will choke on the first complex assignment,
while less naive but still broken implementations will fail when
the torture test is executed at class scope.
And yes, that's a perfectly well-defined assignment statement. Insane,
you might rightly say, but legal::
>>> def f(x): return x
...
>>> x = 42
>>> b = {}
>>> a = b[f(a)] = x
>>> a
42
>>> b
{42: 42}
Possible Implementation Strategy
================================
@ -256,6 +362,9 @@ References
.. [2] http://mail.python.org/pipermail/python-ideas/2010-July/007584.html
.. [3] http://mail.python.org/pipermail/python-ideas/2009-July/005132.html
.. [4] http://mail.python.org/pipermail/python-ideas/2010-July/007596.html
Copyright
=========