Switch back to named functions, since the Ellipsis version degenerated badly for more complex cases. Will revisit this in the 3.4 timeframe

This commit is contained in:
Nick Coghlan 2012-02-23 01:22:14 +10:00
parent 093018129f
commit 05da204481
1 changed files with 106 additions and 103 deletions

View File

@ -7,7 +7,7 @@ Status: Deferred
Type: Standards Track
Content-Type: text/x-rst
Created: 2011-10-13
Python-Version: 3.x
Python-Version: 3.4
Post-History: 2011-10-13
Resolution: TBD
@ -18,12 +18,14 @@ Abstract
This PEP proposes the addition of a new ``in`` statement that accepts a
statement local function or class definition.
The statement allows the use of the Ellipsis literal (``...``) to make a
forward reference to a trailing anonymous function definition.
The statement accepts a single simple statement that can make a forward
reference to a trailing function or class definition.
This new statement is designed to be used whenever a "one-shot" function is
needed, and the meaning of the function is conveyed clearly by the context
and assigning a name can actually reduce clarity rather than increasing it.
This new statement is designed to be used whenever a "one-shot" function or
class is needed, and placing the function or class definition before the
statement that uses it actually makes the code harder to read. It also
avoids any name shadowing concerns by making sure the new name is visible
only to the statement in the ``in`` clause.
This PEP is based heavily on many of the ideas in PEP 3150 (Statement Local
Namespaces) so some elements of the rationale will be familiar to readers of
@ -37,14 +39,14 @@ Before diving into the long history of this problem and the detailed
rationale for this specific proposed solution, here are a few simple
examples of the kind of code it is designed to simplify.
As a trivial example, weakref callbacks could be defined as follows::
As a trivial example, a weakref callback could be defined as follows::
in x = weakref.ref(target, ...)
def ...(obj):
in x = weakref.ref(target, report_destruction)
def report_destruction(obj):
print("{} is being destroyed".format(obj))
This contrasts with the current repetitive "out of order" syntax for this
operation::
This contrasts with the current (conceptually) "out of order" syntax for
this operation::
def report_destruction(obj):
print("{} is being destroyed".format(obj))
@ -54,11 +56,19 @@ operation::
That structure is OK when you're using the callable multiple times, but
it's irritating to be forced into it for one-off operations.
If the repetition of the name seems especially annoying, then a throwaway
name like ``f`` can be used instead::
in x = weakref.ref(target, f)
def f(obj):
print("{} is being destroyed".format(obj))
Similarly, a sorted operation on a particularly poorly defined type could
now be defined as::
in sorted_list = sorted(original, key=...)
def ...(item):
in sorted_list = sorted(original, key=f)
def f(item):
try:
return item.calc_sort_order()
except NotSortableError:
@ -76,8 +86,8 @@ Rather than::
And early binding semantics in a list comprehension could be attained via::
in funcs = [...(i) for i in range(10)]
def ...(i):
in funcs = [adder(i) for i in range(10)]
def adder(i):
return lambda x: x + i
@ -88,28 +98,29 @@ This PEP proposes the addition of a new ``in`` statement that is a variant
of the existing class and function definition syntax.
The new ``in`` clause replaces the decorator lines, and allows forward
references to the trailing function or class definition with the ``...``
literal syntax.
references to the trailing function or class definition.
The trailing function or class definition is always anonymous - the provide
a visual link with the forward reference, the ``...`` literal is always
given as the "name" of the class or function.
The trailing function or class definition is always named - the name of
the trailing definition is then used to make the forward reference from the
preceding statement.
The ``in`` clause is allowed to contain any simple statement (including those
that don't make any sense in that context - while such code would be legal,
there wouldn't be any point in writing it). This permissive structure is
easier to define and easier to explain, but a more restrictive approach that
only permits operations that "make sense" would also be possible (see PEP
3150 for a list of possible candidates)
that don't make any sense in that context, such as ``pass`` - while such code
would be legal, there wouldn't be any point in writing it). This permissive
structure is easier to define and easier to explain, but a more restrictive
approach that only permits operations that "make sense" would also be
possible (see PEP 3150 for a list of possible candidates).
The Ellipsis literal ``...`` would be repurposed inside the ``in`` clause
to refer to the anonymous function or class being defined. The Ellipsis
builtin itself can still be accessed by name from an ``in`` clause if
necessary.
The ``in`` statement will not create a new scope - all name binding
operations aside from the trailing function or class definition will affect
the containing scope.
As functions or classes defined for an ``in`` statement are always
anonymous, local name binding takes place only if the ``in`` clause
includes an assignment.
The name used in the trailing function or class definition is only visible
from the associated ``in`` clause, and behaves as if it was an ordinary
variable defined in that scope. If any nested scopes are created in either
the ``in`` clause or the trailing function or class definition, those scopes
will see the trailing function or class definition rather than any other
bindings for that name in the containing scope.
Background
@ -121,10 +132,11 @@ block functionality for me to finally understand why this bugs people
so much: Python's demand that the function be named and introduced
before the operation that needs it breaks the developer's flow of thought.
They get to a point where they go "I need a one-shot operation that does
<X>", and instead of being able to just *say* that, they instead have to back
up, name a function to do <X>, then call that function from the operation
they actually wanted to do in the first place. Lambda expressions can help
sometimes, but they're no substitute for being able to use a full suite.
<X>", and instead of being able to just *say* that directly, they instead
have to back up, name a function to do <X>, then call that function from
the operation they actually wanted to do in the first place. Lambda
expressions can help sometimes, but they're no substitute for being able to
use a full suite.
Ruby's block syntax also heavily inspired the style of the solution in this
PEP, by making it clear that even when limited to *one* anonymous function per
@ -140,13 +152,19 @@ the heavy lifting:
However, adopting Ruby's block syntax directly won't work for Python, since
the effectiveness of Ruby's blocks relies heavily on various conventions in
the way functions are *defined* (specifically, Ruby's ``yield`` syntax to
call blocks directly and the ``&arg`` mechanism to accept a block as a
the way functions are *defined* (specifically, using Ruby's ``yield`` syntax
to call blocks directly and the ``&arg`` mechanism to accept a block as a
function's final argument).
Since Python has relied on named functions for so long, the signatures of
APIs that accept callbacks are far more diverse, thus requiring a solution
that allows anonymous functions to be slotted in at the appropriate location.
that allows one-shot functions to be slotted in at the appropriate location.
The approach taken in this PEP is to retain the requirement to name the
function explicitly, but allow the relative order of the definition and the
statement that references it to be changed to match the developer's flow of
thought. The rationale is essentially the same as that used when introducing
decorators, but covering a broader set of applications.
Relation to PEP 3150
@ -162,8 +180,9 @@ with something else (like assigning the result of the function to a value).
This PEP also achieves most of the other effects described in PEP 3150
without introducing a new brainbending kind of scope. All of the complex
scoping rules in PEP 3150 are replaced in this PEP with a simple forward
reference to the associated function or class definition.
scoping rules in PEP 3150 are replaced in this PEP with allowing a forward
reference to the associated function or class definition without creating an
actual name binding in the current scope.
Keyword Choice
@ -172,28 +191,31 @@ Keyword Choice
The proposal definitely requires *some* kind of prefix to avoid parsing
ambiguity and backwards compatibility problems with existing constructs.
It also needs to be clearly highlighted to readers, since it declares that
the following piece of code is going to be executed out of order.
the following piece of code is going to be executed only after the trailing
function or class definition has been executed.
The ``in`` keyword was chosen as an existing keyword that can be used to
denote the concept of a forward reference.
For functions, the construct is intended to be read as "in <this statement
that references "..."> define "..." as this function".
that references NAME> define NAME as a function that does <operation>".
The mapping to English prose isn't as clean for the class definition case,
The mapping to English prose isn't as obvious for the class definition case,
but the concept remains the same.
Better Debugging Support for Anonymous Functions and Classes
============================================================
Better Debugging Support for Functions and Classes with Short Names
===================================================================
One of the objections to widespread use of lambda expressions is that they
have a negative effect on traceback intelligibility and other aspects of
introspection.
introspection. Similarly objections are raised regarding constructs that
promote short, cryptic function names (including this one, which requires
that the name of the trailing definition be supplied at least twice)
However, the introduction of qualified names in PEP 3155 means that
anonymous functions in different scopes will now have different
representations. For example::
However, the introduction of qualified names in PEP 3155 means that even
anonymous classes and functions will now have different representations if
they occur in different scopes. For example::
>>> def f():
... return lambda: y
@ -201,22 +223,17 @@ representations. For example::
>>> f()
<function f.<locals>.<lambda> at 0x7f6f46faeae0>
Anonymous function within the *same* scope will still share representations
(aside from the object ID), but this is still a major improvement over the
historical situation where everything *except* the object ID was identical.
The anonymous functions and classes created by the new statement will use
the metaname ``<anon>``.
Anonymous functions (or functions that share a name) within the *same* scope
will still share representations (aside from the object ID), but this is
still a major improvement over the historical situation where everything
*except* the object ID was identical.
Syntax Change
=============
New::
in_stmt: in_prefix (in_classdef|in_funcdef)
in_prefix: 'in' simple_stmt
in_funcdef: 'def' '...' parameters ['->' test] ':' suite
in_classdef: 'class' '...' ['(' [arglist] ')'] ':' suite
in_stmt: 'in' simple_stmt (classdef|funcdef)
Grammar: http://hg.python.org/cpython/file/default/Grammar/Grammar
@ -232,9 +249,10 @@ definition and the statement that references it, so it should just be a
matter of emitting the two operations out of order and using a hidden
variable to link up any references.
The one potentially tricky part is changing the meaning of the Ellipsis
literal notation while within the scope of the ``in`` clause, but that
shouldn't be too hard to address within the compiler.
The one potentially tricky part is changing the meaning of the references to
the statement local function or namespace while within the scope of the
``in`` statement, but that shouldn't be too hard to address by maintaining
some additional state within the compiler.
More Examples
@ -250,8 +268,8 @@ Calculating attributes without polluting the local namespace (from os.py)::
del _createenviron
# Becomes:
in environ = ...()
def ...():
in environ = _createenviron()
def _createenviron():
... # 27 line function
Loop early binding::
@ -260,56 +278,25 @@ Loop early binding::
funcs = [(lambda x, i=i: x + i) for i in range(10)]
# Becomes:
in funcs = [...(i) for i in range(10)]
def ...(i):
in funcs = [adder(i) for i in range(10)]
def adder(i):
return lambda x: x + i
# Or even:
in funcs = [...(i) for i in range(10)]
def ...(i):
in return ...
def ...(x):
return x + i
Statement local namespace::
# OK, this definitely looks weird and needs further thought...
in c = math.sqrt(....a*....a + ....b*....b)
class ...:
a = calculate_a()
b = calculate_b()
Alternative Idea
================
As the statement local namespace example shows, using ```...`` for the
forward reference doesn't play nicely with attribute references on the
anonymous object. The doubly nested example also shows that overuse can
lead to readability disappearing in a mass of dots.
An alternative approach would be to use a similar hidden variable
implementation strategy to implement a *single* statement local variable
for use as the forward reference. Getting the scoping right could be
challenging, but it should still be feasible.
Then the two problematic examples could be written as::
in funcs = [f(i) for i in range(10)]
def f(i):
in funcs = [adder(i) for i in range(10)]
def adder(i):
in return incr
def incr(x):
return x + i
A trailing class can be used as a statement local namespace::
# Evaluate subexpressions only once
in c = math.sqrt(x.a*x.a + x.b*x.b)
class x:
a = calculate_a()
b = calculate_b()
With the name not actually being bound in the local scope, it isn't
necessary to worry about name collisions, but meaningful names can still be
used to improve readability.
Reference Implementation
========================
@ -332,6 +319,22 @@ A previous incarnation of this PEP (see [1]) proposed a much uglier syntax
that (quite rightly) was not well received. The current proposal is
significantly easier both to read and write.
A more recent variant always used ``...`` for forward references, along
with genuinely anonymous function and class definitions. However, this
degenerated quickly into a mass of unintelligible dots in more complex
cases::
in funcs = [...(i) for i in range(10)]
def ...(i):
in return ...
def ...(x):
return x + i
in c = math.sqrt(....a*....a + ....b*....b)
class ...:
a = calculate_a()
b = calculate_b()
References
==========