Explicitly recast this proposal as a new kind of decorator clause
This commit is contained in:
parent
13b2b1f160
commit
8257981ad1
129
pep-0403.txt
129
pep-0403.txt
|
@ -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
|
||||
==========
|
||||
|
|
Loading…
Reference in New Issue