python-peps/pep-0403.txt

316 lines
10 KiB
Plaintext

PEP: 403
Title: Prefix syntax for post function definition operations
Version: $Revision$
Last-Modified: $Date$
Author: Nick Coghlan <ncoghlan@gmail.com>
Status: Withdrawn
Type: Standards Track
Content-Type: text/x-rst
Created: 2011-10-13
Python-Version: 3.x
Post-History: 2011-10-13
Resolution: TBD
Abstract
========
This PEP proposes the addition of ``postdef`` as a new function prefix
syntax (analogous to decorators) that permits the execution of a single simple
statement (potentially including substatements separated by semi-colons) after
In addition, the new syntax would allow the 'def' keyword to be used to refer
to the function being defined without needing to repeat the name.
When the 'postdef' prefix syntax is used, the associated statement would be
executed *in addition to* the normal local name binding implicit in function
definitions. Any name collision are expected to be minor, analagous to those
encountered with ``for`` loop iteration variables.
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
that PEP. That PEP has now been withdrawn in favour of this one.
PEP Withdrawal
==============
The python-ideas thread discussing this PEP [1]_ persuaded me that it was
essentially am unnecessarily cryptic, wholly inferior version of PEP 3150's
statement local namespaces. The discussion also resolved some of my concerns
with PEP 3150, so I am withdrawing this more limited version of the idea in
favour of resurrecting the original concept.
Basic Examples
==============
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::
postdef x = weakref.ref(target, def)
def report_destruction(obj):
print("{} is being destroyed".format(obj))
This contrasts with the current repetitive "out of order" syntax for this
operation::
def report_destruction(obj):
print("{} is being destroyed".format(obj))
x = weakref.ref(target, report_destruction)
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.
Similarly, a sorted operation on a particularly poorly defined type could
now be defined as::
postdef sorted_list = sorted(original, key=def)
def force_sort(item):
try:
return item.calc_sort_order()
except NotSortableError:
return float('inf')
Rather than::
def force_sort(item):
try:
return item.calc_sort_order()
except NotSortableError:
return float('inf')
sorted_list = sorted(original, key=force_sort)
And early binding semantics in a list comprehension could be attained via::
postdef funcs = [def(i) for i in range(10)]
def make_incrementor(i):
postdef return def
def incrementor(x):
return x + i
Proposal
========
This PEP proposes the addition of an optional block prefix clause to the
syntax for function and class definitions.
This block prefix would be introduced by a leading ``postdef`` and would be
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)
The function definition keyword ``def`` would be repurposed inside the block prefix
to refer to the function being defined.
When a block prefix is provided, the standard local name binding implicit
in the function definition still takes place.
Background
==========
The question of "multi-line lambdas" has been a vexing one for many
Python users for a very long time, and it took an exploration of Ruby's
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.
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
statement, anonymous functions could still be incredibly useful. Consider how
many constructs Python has where one expression is responsible for the bulk of
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)
* array broadcast operations in NumPy
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
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.
Relation to PEP 3150
====================
PEP 3150 (Statement Local Namespaces) described its primary motivation
as being to elevate ordinary assignment statements to be on par with ``class``
and ``def`` statements where the name of the item to be defined is presented
to the reader in advance of the details of how the value of that item is
calculated. This PEP achieves the same goal in a different way, by allowing
the simple name binding of a standard function definition to be replaced
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 the simple ``def``
reference to the associated function definition.
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 'postdef' keyword was chosen as a literal explanation of exactly what
the new clause does: execute the specified statement *after* the associated
function definition, even though it is physically written *before* the
definition in the source code.
Requirement to Name Functions
=============================
One of the objections to widespread use of lambda expressions is that they
have an atrocious effect on traceback intelligibility and other aspects of
introspection. Accordingly, this PEP requires that even throwaway functions
be given some kind of name.
To help encourage the use of meaningful names without users having to repeat
themselves, the PEP suggests the provision of the ``def`` shorthand reference
to the current function from the ``postdef`` clause.
Syntax Change
=============
Current::
atom: ('(' [yield_expr|testlist_comp] ')' |
'[' [testlist_comp] ']' |
'{' [dictorsetmaker] '}' |
NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False')
Changed::
atom: ('(' [yield_expr|testlist_comp] ')' |
'[' [testlist_comp] ']' |
'{' [dictorsetmaker] '}' |
NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False' | 'def')
New::
blockprefix: 'postdef' simple_stmt
block: blockprefix funcdef
The above is the general idea, but I suspect that the change to the 'atom'
definition may cause an ambiguity problem in the parser when it comes to
detecting function definitions. So the actual implementation may need to be
more complex than that.
Grammar: http://hg.python.org/cpython/file/default/Grammar/Grammar
Possible Implementation Strategy
================================
This proposal has one titanic advantage over PEP 3150: implementation
should be relatively straightforward.
The post definition statement can be incorporated into the AST for the
function node and simply visited out of sequence.
The one potentially tricky part is working out how to allow the dual
use of 'def' without rewriting half the grammar definition.
More Examples
=============
Calculating attributes without polluting the local namespace (from os.py)::
# Current Python (manual namespace cleanup)
def _createenviron():
... # 27 line function
environ = _createenviron()
del _createenviron
# Becomes:
postdef environ = def()
def _createenviron():
... # 27 line function
Loop early binding::
# Current Python (default argument hack)
funcs = [(lambda x, i=i: x + i) for i in range(10)]
# Becomes:
postdef funcs = [def(i) for i in range(10)]
def make_incrementor(i):
return lambda x: x + i
# Or even:
postdef funcs = [def(i) for i in range(10)]
def make_incrementor(i):
postdef return def
def incrementor(x):
return x + i
Reference Implementation
========================
None as yet.
Acknowledgements
================
Huge thanks to Gary Bernhardt for being blunt in pointing out that I had no
idea what I was talking about in criticising Ruby's blocks, kicking off a
rather enlightening process of investigation.
Even though this PEP has been withdrawn, the process of writing and arguing
in its favour has been quite influential on the future direction of PEP 3150.
References
==========
.. [1] Start of python-ideas thread:
http://mail.python.org/pipermail/python-ideas/2011-October/012276.html
Copyright
=========
This document has been placed in the public domain.
..
Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
coding: utf-8
End: