Updates based on python-ideas discussion
This commit is contained in:
parent
d42a5e3168
commit
f9ab5fc41e
213
pep-3150.txt
213
pep-3150.txt
|
@ -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
|
||||
=========
|
||||
|
|
Loading…
Reference in New Issue