Explicitly recast this proposal as a new kind of decorator clause

This commit is contained in:
Nick Coghlan 2012-09-03 21:19:27 +10:00
parent 13b2b1f160
commit 8257981ad1
1 changed files with 88 additions and 41 deletions

View File

@ -1,5 +1,5 @@
PEP: 403
Title: Statement local functions and classes
Title: General purpose decorator clause
Version: $Revision$
Last-Modified: $Date$
Author: Nick Coghlan <ncoghlan@gmail.com>
@ -15,17 +15,18 @@ Resolution: TBD
Abstract
========
This PEP proposes the addition of a new ``in`` statement that accepts a
statement local function or class definition.
This PEP proposes the addition of a new ``@in`` decorator clause that makes
it possible to override the name binding step of a function or class
definition.
The statement accepts a single simple statement that can make a forward
reference to a trailing function or class definition.
The new clause accepts a single simple statement that can make a forward
reference to decorated function or class definition.
This new statement is designed to be used whenever a "one-shot" function or
This new clause 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.
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
@ -41,7 +42,7 @@ examples of the kind of code it is designed to simplify.
As a trivial example, a weakref callback could be defined as follows::
in x = weakref.ref(target, report_destruction)
@in x = weakref.ref(target, report_destruction)
def report_destruction(obj):
print("{} is being destroyed".format(obj))
@ -59,7 +60,7 @@ 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)
@in x = weakref.ref(target, f)
def f(obj):
print("{} is being destroyed".format(obj))
@ -67,7 +68,7 @@ name like ``f`` can be used instead::
Similarly, a sorted operation on a particularly poorly defined type could
now be defined as::
in sorted_list = sorted(original, key=f)
@in sorted_list = sorted(original, key=f)
def f(item):
try:
return item.calc_sort_order()
@ -86,7 +87,7 @@ Rather than::
And early binding semantics in a list comprehension could be attained via::
in funcs = [adder(i) for i in range(10)]
@in funcs = [adder(i) for i in range(10)]
def adder(i):
return lambda x: x + i
@ -94,34 +95,50 @@ And early binding semantics in a list comprehension could be attained via::
Proposal
========
This PEP proposes the addition of a new ``in`` statement that is a variant
of the existing class and function definition syntax.
This PEP proposes the addition of a new ``@in`` clause that is a variant
of the existing class and function decorator syntax.
The new ``in`` clause replaces the decorator lines, and allows forward
The new ``@in`` clause precedes the decorator lines, and allows forward
references to the trailing function or class definition.
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.
``@in`` clause.
The ``in`` clause is allowed to contain any simple statement (including those
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 ``@in`` clause is allowed to contain any simple statement (including
those 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 ``in`` statement will not create a new scope - all name binding
The ``@in`` clause will not create a new scope - all name binding
operations aside from the trailing function or class definition will affect
the containing scope.
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
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
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.
In a very real sense, this proposal is about making it possible to override
the implicit "name = <defined function or class>" name binding operation
that is part of every function or class definition, specifically in those
cases where the local name binding isn't actually needed.
Under this PEP, an ordinary class or function definition::
def name():
...
would be equivalent to::
@in name = name
def name():
...
Background
==========
@ -147,7 +164,7 @@ the heavy lifting:
* comprehensions, generator expressions, map(), filter()
* key arguments to sorted(), min(), max()
* partial function application
* provision of callbacks (e.g. for weak references)
* provision of callbacks (e.g. for weak references or aysnchronous IO)
* array broadcast operations in NumPy
However, adopting Ruby's block syntax directly won't work for Python, since
@ -197,6 +214,11 @@ 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.
The ``@`` prefix was included in order to exploit the fact that Python
programmers are already used to decorator syntax as an indication of
out of order execution, where the function or class is actually defined
*first* and then decorators are applied in reverse order.
For functions, the construct is intended to be read as "in <this statement
that references NAME> define NAME as a function that does <operation>".
@ -231,9 +253,9 @@ still a major improvement over the historical situation where everything
Syntax Change
=============
New::
Syntactically, only one new grammar rule is needed::
in_stmt: 'in' simple_stmt (classdef|funcdef)
in_stmt: '@in' simple_stmt decorated
Grammar: http://hg.python.org/cpython/file/default/Grammar/Grammar
@ -244,10 +266,10 @@ Possible Implementation Strategy
This proposal has at least one titanic advantage over PEP 3150:
implementation should be relatively straightforward.
The AST for the ``in`` statement will include both the function or class
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 ``@in`` clause will be included in the AST for the associated function or
class definition and the statement that references it. When the ``@in``
clause is present, it will be emitted in place of the local name binding
operation normally implied by a function or class definition.
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
@ -268,7 +290,7 @@ Calculating attributes without polluting the local namespace (from os.py)::
del _createenviron
# Becomes:
in environ = _createenviron()
@in environ = _createenviron()
def _createenviron():
... # 27 line function
@ -278,25 +300,45 @@ Loop early binding::
funcs = [(lambda x, i=i: x + i) for i in range(10)]
# Becomes:
in funcs = [adder(i) for i in range(10)]
@in funcs = [adder(i) for i in range(10)]
def adder(i):
return lambda x: x + i
return lambda x: x + i
# Or even:
in funcs = [adder(i) for i in range(10)]
@in funcs = [adder(i) for i in range(10)]
def adder(i):
in return incr
def incr(x):
return x + 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)
@in c = math.sqrt(x.a*x.a + x.b*x.b)
class x:
a = calculate_a()
b = calculate_b()
a = calculate_a()
b = calculate_b()
A function can be bound directly to a location which isn't a valid
identifier::
@in dispatch[MyClass] = f
def f():
...
Constructs that verge on decorator abuse can be eliminated::
# Current Python
@call
def f():
...
# Becomes:
@in f()
def f():
...
Reference Implementation
========================
@ -335,6 +377,11 @@ cases::
a = calculate_a()
b = calculate_b()
Another past alternative omitted the ``@`` prefix. However, without that
prefix, the bare ``in`` keyword didn't associate the clause strongly
enough with the subsequent function or class definition. Reusing the
decorator prefix and explicitly characterising the new construct as a kind
of decorator clause should address that problem.
References
==========