Some new ideas for PEP 3150

This commit is contained in:
Nick Coghlan 2013-08-03 02:42:54 +10:00
parent 311a487701
commit 91f171864b
1 changed files with 87 additions and 33 deletions

View File

@ -19,9 +19,11 @@ This PEP proposes the addition of an optional ``given`` clause to several
Python statements that do not currently have an associated code suite. This
clause will create a statement local namespace for additional names that are
accessible in the associated statement, but do not become part of the
containing namespace. To permit a sane implementation strategy, forward
references to names from the ``given`` clause will need to be marked
explicitly.
containing namespace.
Adoption of a new symbol, ``?``, is proposed to denote a forward reference
to the namespace created by running the associated code suite. It will be
a reference to a ``types.SimpleNamespace`` object.
The primary motivation is to enable a more declarative style of programming,
where the operation to be performed is presented to the reader first, and the
@ -72,12 +74,16 @@ 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::
sorted_data = sorted(data, key=.sort_key) given:
sorted_data = sorted(data, key=?.sort_key) given:
def sort_key(item):
return item.attr1, item.attr2
The leading ``.`` on ``.sort_key`` indicates to the compiler that this
is a forward reference to a name defined in the ``given`` clause.
The new symbol ``?`` is used to refer to the given namespace. It would be a
``types.SimpleNamespace`` instance, so ``?.sort_key`` functions as
a forward reference to a name defined in the ``given`` clause.
A docstring would be permitted in the given clause, and would be attached
to the result namespace as its ``__doc__`` attribute.
The ``pass`` statement is included to provide a consistent way to skip
inclusion of a meaningful expression in the header line. While this is not
@ -94,7 +100,7 @@ binding operations in the header line::
# Explicit early binding via given clause
seq = []
for i in range(10):
seq.append(.f) given i=i:
seq.append(.f) given i=i in:
def f():
return i
assert [f() for f in seq] == list(range(10))
@ -105,7 +111,7 @@ Semantics
The following statement::
op(.f, .g) given bound_a=a, bound_b=b:
op(?.f, ?.g) given bound_a=a, bound_b=b in:
def f():
return bound_a + bound_b
def g():
@ -121,9 +127,10 @@ hidden compiler variable or simply an entry on the interpreter stack)::
return bound_a + bound_b
def g():
return bound_a - bound_b
return f, g
__ref1, __ref2 = __scope(__arg1)
op(__ref1, __ref2)
return types.SimpleNamespace(**locals())
__ref = __scope(__arg1, __arg2)
__ref.__doc__ = __scope.__doc__
op(__ref.f, __ref.g)
A ``given`` clause is essentially a nested function which is created and
then immediately executed. Unless explicitly passed in, names are looked
@ -158,7 +165,7 @@ New::
yield_stmt: yield_expr [given_clause]
raise_stmt: 'raise' [test ['from' test]] [given_clause]
assert_stmt: 'assert' test [',' test] [given_clause]
given_clause: "given" (NAME '=' test)* ":" suite
given_clause: "given" [(NAME '=' test)+ "in"]":" suite
(Note that ``expr_stmt`` in the grammar is a slight misnomer, as it covers
assignment and augmented assignment in addition to simple expression
@ -207,7 +214,7 @@ For reference, here are the current definitions at that level::
flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt
In addition to the above changes, the definition of ``atom`` would be changed
to also allow ``"." NAME``. The restriction of this usage to statements with
to also allow ``?``. The restriction of this usage to statements with
an associated ``given`` clause would be handled by a later stage of the
compilation process (likely AST construction, which already enforces
other restrictions where the grammar is overly permissive in order to
@ -277,13 +284,14 @@ without reading the body of the suite.
However, while they are the initial motivating use case, limiting this
feature solely to simple assignments would be overly restrictive. Once the
feature is defined at all, it would be quite arbitrary to prevent its use
for augmented assignments, return statements, yield expressions and
arbitrary expressions that may modify the application state.
for augmented assignments, return statements, yield expressions,
comprehensions and arbitrary expressions that may modify the
application state.
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()``.
like ``sorted()`` or in callback based event-driven programming.
In module and class level code, the ``given`` clause will serve as a
clear and reliable replacement for usage of the ``del`` statement to keep
@ -350,7 +358,7 @@ container comprehensions::
# would be equivalent to
seq2 = .result given seq=seq:
seq2 = ?.result given seq=seq:
result = []
for y in seq:
if p(y):
@ -367,7 +375,7 @@ Not that, unlike PEP 403, the current version of this PEP *cannot*
provide a precisely equivalent expansion for a generator expression. The
closest it can get is to define an additional level of scoping::
seq2 = .g(seq) given:
seq2 = ?.g(seq) given:
def g(seq):
for y in seq:
if p(y):
@ -375,6 +383,22 @@ closest it can get is to define an additional level of scoping::
if q(x):
yield x
This limitation could be remedied by permitting the given clause to be
a generator function, in which case ? would refer to a generator-iterator
object rather than a simple namespace::
seq2 = ? given seq=seq in:
for y in seq:
if p(y):
for x in y:
if q(x):
yield x
However, this would make the meaning of "?" quite ambiguous, even more so
than is already the case for the meaning of ``def`` statements (which will
usually have a docstring indicating whether or not a function definition is
actually a generator)
Explaining Decorator Clause Evaluation and Application
------------------------------------------------------
@ -477,14 +501,19 @@ what the language allows them to express.
I believe the proposal in this PEP would finally let Python get close to the
"executable pseudocode" bar for the kind of thought expressed above::
sorted_list = sorted(original, key=.sort_key) given:
def sort_key(item):
sorted_list = sorted(original, key=?.key) given:
def key(item):
return item.attr1, item.attr2
Everything is in the same order as it was in the user's original thought, the
only addition they have to make is to give the sorting criteria a name so that
the usage can be linked up to the subsequent definition.
Everything is in the same order as it was in the user's original thought, and
they don't even need to come up with a name for the sorting criteria: it is
possible to reuse the keyword argument name directly.
A possible enhancement to those proposal would be to provide a convenient
shorthand syntax to say "use the given clause contents as keyword
arguments". Even without dedicated syntax, that can be written simply as
``**vars(?)``.
Harmful to Introspection
~~~~~~~~~~~~~~~~~~~~~~~~
@ -516,7 +545,7 @@ world code is genuinely enhanced.
This is more of a deficiency in the PEP rather than the idea, though. If
it wasn't a real world problem, we wouldn't get so many complaints about
the lack of multi-line lambda support and Ruby's block construct
probaly wouldn't be quite so popular.
probably wouldn't be quite so popular.
Open Questions
@ -525,9 +554,12 @@ Open Questions
Syntax for Forward References
-----------------------------
The leading ``.`` arguably fails the "syntax shall not look like grit on
Uncle Tim's monitor" test. However, it does have the advantages of being
easy to type and already having an association with namespaces.
The ``?`` symbol is proposed for forward references to the given namespace
as it is short, currently unused and suggests "there's something missing
here that will be filled in later".
The proposal in the PEP doesn't neatly parallel any existing Python feature,
so reusing an already used symbol has been deliberately avoided.
Handling of ``nonlocal`` and ``global``
@ -541,8 +573,8 @@ Alternatively, they could be defined as operating as if the anonymous
functions were defined as in the expansion above.
Detailed Semantics #3: Handling of ``break`` and ``continue``
-------------------------------------------------------------
Handling of ``break`` and ``continue``
--------------------------------------
``break`` and ``continue`` will operate as if the anonymous functions were
defined as in the expansion above. They will be syntax errors if they occur
@ -561,6 +593,25 @@ they appear within a ``def`` statement within that suite.
Examples
========
Defining callbacks for event driven programming::
# Current Python (definition before use)
def cb(sock):
# Do something with socket
def eb(exc):
logging.exception(
"Failed connecting to %s:%s", host, port)
loop.create_connection((host, port), cb, eb) given:
# Becomes:
loop.create_connection((host, port), ?.cb, ?.eb) given:
def cb(sock):
# Do something with socket
def eb(exc):
logging.exception(
"Failed connecting to %s:%s", host, port)
Defining "one-off" classes which typically only have a single instance::
# Current Python (instantiation after definition)
@ -579,7 +630,7 @@ Defining "one-off" classes which typically only have a single instance::
... # However many lines
# Becomes:
public_name = .MeaningfulClassName(*params) given:
public_name = ?.MeaningfulClassName(*params) given:
class MeaningfulClassName():
... # Should trawl the stdlib for an example of doing this
@ -593,7 +644,7 @@ Calculating attributes without polluting the local namespace (from os.py)::
del _createenviron
# Becomes:
environ = ._createenviron() given:
environ = ?._createenviron() given:
def _createenviron():
... # 27 line function
@ -606,7 +657,7 @@ Replacing default argument hack (from functools.lru_cache)::
return decorating_function
# Becomes:
return .decorating_function given:
return ?.decorating_function given:
# Cell variables rather than locals, but should give similar speedup
tuple, sorted, len, KeyError = tuple, sorted, len, KeyError
def decorating_function(user_function):
@ -701,6 +752,9 @@ References
.. [9] Possible PEP 3150 style guidelines (#2):
http://mail.python.org/pipermail/python-ideas/2011-October/012341.html
.. [10] Multi-line lambdas (again!)
http://mail.python.org/pipermail/python-ideas/2013-August/022526.html
Copyright
=========