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 PEP: 403
Title: Statement local functions and classes Title: General purpose decorator clause
Version: $Revision$ Version: $Revision$
Last-Modified: $Date$ Last-Modified: $Date$
Author: Nick Coghlan <ncoghlan@gmail.com> Author: Nick Coghlan <ncoghlan@gmail.com>
@ -15,17 +15,18 @@ Resolution: TBD
Abstract Abstract
======== ========
This PEP proposes the addition of a new ``in`` statement that accepts a This PEP proposes the addition of a new ``@in`` decorator clause that makes
statement local function or class definition. 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 The new clause accepts a single simple statement that can make a forward
reference to a trailing function or class definition. 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 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 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 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 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 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:: 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): def report_destruction(obj):
print("{} is being destroyed".format(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 If the repetition of the name seems especially annoying, then a throwaway
name like ``f`` can be used instead:: name like ``f`` can be used instead::
in x = weakref.ref(target, f) @in x = weakref.ref(target, f)
def f(obj): def f(obj):
print("{} is being destroyed".format(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 Similarly, a sorted operation on a particularly poorly defined type could
now be defined as:: now be defined as::
in sorted_list = sorted(original, key=f) @in sorted_list = sorted(original, key=f)
def f(item): def f(item):
try: try:
return item.calc_sort_order() return item.calc_sort_order()
@ -86,7 +87,7 @@ Rather than::
And early binding semantics in a list comprehension could be attained via:: 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): def adder(i):
return lambda x: x + i return lambda x: x + i
@ -94,34 +95,50 @@ And early binding semantics in a list comprehension could be attained via::
Proposal Proposal
======== ========
This PEP proposes the addition of a new ``in`` statement that is a variant This PEP proposes the addition of a new ``@in`` clause that is a variant
of the existing class and function definition syntax. 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. references to the trailing function or class definition.
The trailing function or class definition is always named - the name of 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 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 The ``@in`` clause is allowed to contain any simple statement (including
that don't make any sense in that context, such as ``pass`` - while such code those that don't make any sense in that context, such as ``pass`` - while
would be legal, there wouldn't be any point in writing it). This permissive such code would be legal, there wouldn't be any point in writing it). This
structure is easier to define and easier to explain, but a more restrictive permissive structure is easier to define and easier to explain, but a more
approach that only permits operations that "make sense" would also be restrictive approach that only permits operations that "make sense" would
possible (see PEP 3150 for a list of possible candidates). 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 operations aside from the trailing function or class definition will affect
the containing scope. the containing scope.
The name used in the trailing function or class definition is only visible 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 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 will see the trailing function or class definition rather than any other
bindings for that name in the containing scope. 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 Background
========== ==========
@ -147,7 +164,7 @@ the heavy lifting:
* comprehensions, generator expressions, map(), filter() * comprehensions, generator expressions, map(), filter()
* key arguments to sorted(), min(), max() * key arguments to sorted(), min(), max()
* partial function application * 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 * array broadcast operations in NumPy
However, adopting Ruby's block syntax directly won't work for Python, since 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 The ``in`` keyword was chosen as an existing keyword that can be used to
denote the concept of a forward reference. 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 For functions, the construct is intended to be read as "in <this statement
that references NAME> define NAME as a function that does <operation>". 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 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 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: This proposal has at least one titanic advantage over PEP 3150:
implementation should be relatively straightforward. implementation should be relatively straightforward.
The AST for the ``in`` statement will include both the function or class The ``@in`` clause will be included in the AST for the associated function or
definition and the statement that references it, so it should just be a class definition and the statement that references it. When the ``@in``
matter of emitting the two operations out of order and using a hidden clause is present, it will be emitted in place of the local name binding
variable to link up any references. operation normally implied by a function or class definition.
The one potentially tricky part is changing the meaning of the references to 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 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 del _createenviron
# Becomes: # Becomes:
in environ = _createenviron() @in environ = _createenviron()
def _createenviron(): def _createenviron():
... # 27 line function ... # 27 line function
@ -278,25 +300,45 @@ Loop early binding::
funcs = [(lambda x, i=i: x + i) for i in range(10)] funcs = [(lambda x, i=i: x + i) for i in range(10)]
# Becomes: # Becomes:
in funcs = [adder(i) for i in range(10)] @in funcs = [adder(i) for i in range(10)]
def adder(i): def adder(i):
return lambda x: x + i return lambda x: x + i
# Or even: # Or even:
in funcs = [adder(i) for i in range(10)] @in funcs = [adder(i) for i in range(10)]
def adder(i): def adder(i):
in return incr @in return incr
def incr(x): def incr(x):
return x + i return x + i
A trailing class can be used as a statement local namespace:: A trailing class can be used as a statement local namespace::
# Evaluate subexpressions only once # 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: class x:
a = calculate_a() a = calculate_a()
b = calculate_b() 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 Reference Implementation
======================== ========================
@ -335,6 +377,11 @@ cases::
a = calculate_a() a = calculate_a()
b = calculate_b() 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 References
========== ==========