PEP 572: Rework the alternatives section into separate categories
This commit is contained in:
parent
d9d878ee6e
commit
af7b76f545
150
pep-0572.rst
150
pep-0572.rst
|
@ -263,14 +263,78 @@ Both of these are forbidden; creating SLNBs in the headers of these statements
|
|||
will result in a SyntaxError.
|
||||
|
||||
|
||||
Alternative proposals and variants
|
||||
==================================
|
||||
Rejected alternative proposals
|
||||
==============================
|
||||
|
||||
Proposals broadly similar to this one have come up frequently on python-ideas.
|
||||
Below are a number of alternative syntaxes, some of them specific to
|
||||
comprehensions, which have been rejected in favour of the one given above.
|
||||
|
||||
1. ``where``, ``let``, or ``given``, in comprehensions only::
|
||||
|
||||
Alternative spellings
|
||||
---------------------
|
||||
|
||||
Broadly the same semantics as the current proposal, but spelled differently.
|
||||
|
||||
1. ``EXPR as NAME`` without parentheses::
|
||||
|
||||
stuff = [[f(x) as y, x/y] for x in range(5)]
|
||||
|
||||
Omitting the parentheses from this PEP's proposed syntax introduces many
|
||||
syntactic ambiguities. Requiring them in all contexts leaves open the
|
||||
option to make them optional in specific situations where the syntax is
|
||||
unambiguous (cf generator expressions as sole parameters in function
|
||||
calls), but there is no plausible way to make them optional everywhere.
|
||||
|
||||
2. Adorning statement-local names with a leading dot::
|
||||
|
||||
stuff = [[(f(x) as .y), x/.y] for x in range(5)]
|
||||
|
||||
This has the advantage that leaked usage can be readily detected, removing
|
||||
some forms of syntactic ambiguity. However, this would be the only place
|
||||
in Python where a variable's scope is encoded into its name, making
|
||||
refactoring harder. This syntax is quite viable, and could be promoted to
|
||||
become the current recommendation if its advantages are found to outweigh
|
||||
its cost.
|
||||
|
||||
3. Adding a ``where:`` to any statement to create local name bindings::
|
||||
|
||||
value = x**2 + 2*x where:
|
||||
x = spam(1, 4, 7, q)
|
||||
|
||||
Execution order is inverted (the indented body is performed first, followed
|
||||
by the "header"). This requires a new keyword, unless an existing keyword
|
||||
is repurposed (most likely ``with:``).
|
||||
|
||||
|
||||
Special-casing conditional statements
|
||||
-------------------------------------
|
||||
|
||||
One of the most popular use-cases is ``if`` and ``while`` statements. Instead
|
||||
of a more general solution, this proposal enhances the syntax of these two
|
||||
statements to add a means of capturing the compared value::
|
||||
|
||||
if re.search(pat, text) as match:
|
||||
print("Found:", match.group(0))
|
||||
|
||||
This works beautifully if and ONLY if the desired condition is based on the
|
||||
truthiness of the captured value. It is thus effective for specific
|
||||
use-cases (regex matches, socket reads that return `''` when done), and
|
||||
completely useless in more complicated cases (eg where the condition is
|
||||
``f(x) < 0`` and you want to capture the value of ``f(x)``). It also has
|
||||
no benefit to list comprehensions.
|
||||
|
||||
Advantages: No syntactic ambiguities. Disadvantages: Answers only a fraction
|
||||
of possible use-cases, even in ``if``/``while`` statements.
|
||||
|
||||
|
||||
Special-casing comprehensions
|
||||
-----------------------------
|
||||
|
||||
Another common use-case is comprehensions (list/set/dict, and genexps). As
|
||||
above, proposals have been made for comprehension-specific solutions.
|
||||
|
||||
1. ``where``, ``let``, or ``given``::
|
||||
|
||||
stuff = [(y, x/y) where y = f(x) for x in range(5)]
|
||||
stuff = [(y, x/y) let y = f(x) for x in range(5)]
|
||||
|
@ -304,58 +368,58 @@ comprehensions, which have been rejected in favour of the one given above.
|
|||
completely different from the meaning as a stand-alone statement, while
|
||||
retaining identical syntax.
|
||||
|
||||
4. ``EXPR as NAME`` without parentheses::
|
||||
Regardless of the spelling chosen, this introduces a stark difference between
|
||||
comprehensions and the equivalent unrolled long-hand form of the loop. It is
|
||||
no longer possible to unwrap the loop into statement form without reworking
|
||||
any name bindings. The only keyword that can be repurposed to this task is
|
||||
``with``, thus giving it sneakily different semantics in a comprehension than
|
||||
in a statement; alternatively, a new keyword is needed, with all the costs
|
||||
therein.
|
||||
|
||||
stuff = [[f(x) as y, x/y] for x in range(5)]
|
||||
Assignment expressions
|
||||
----------------------
|
||||
|
||||
Omitting the parentheses from this PEP's proposed syntax introduces many
|
||||
syntactic ambiguities. Requiring them in all contexts leaves open the
|
||||
option to make them optional in specific situations where the syntax is
|
||||
unambiguous (cf generator expressions as sole parameters in function
|
||||
calls), but there is no plausible way to make them optional everywhere.
|
||||
Rather than creating a statement-local name, these forms of name binding have
|
||||
the exact same semantics as regular assignment: bind to a local name unless
|
||||
there's a ``global`` or ``nonlocal`` declaration.
|
||||
|
||||
5. Adorning statement-local names with a leading dot::
|
||||
Syntax options:
|
||||
|
||||
stuff = [[(f(x) as .y), x/.y] for x in range(5)]
|
||||
1. ``(EXPR as NAME)`` as per the promoted proposal
|
||||
|
||||
This has the advantage that leaked usage can be readily detected, removing
|
||||
some forms of syntactic ambiguity. However, this would be the only place
|
||||
in Python where a variable's scope is encoded into its name, making
|
||||
refactoring harder. This syntax is quite viable, and could be promoted to
|
||||
become the current recommendation if its advantages are found to outweigh
|
||||
its cost.
|
||||
2. C-style ``NAME = EXPR`` in any context
|
||||
|
||||
6. Allowing ``(EXPR as NAME)`` to assign to any form of name.
|
||||
3. A new and dedicated operator with C-like semantics ``NAME := EXPR``
|
||||
|
||||
This is exactly the same as the promoted proposal, save that the name is
|
||||
bound in the same scope that it would otherwise have. Any expression can
|
||||
assign to any name, just as it would if the ``=`` operator had been used.
|
||||
Such variables would leak out of the statement into the enclosing function,
|
||||
subject to the regular behaviour of comprehensions (since they implicitly
|
||||
create a nested function, the name binding would be restricted to the
|
||||
comprehension itself, just as with the names bound by ``for`` loops).
|
||||
The C syntax has been long known to be a bug magnet. The syntactic similarity
|
||||
between ``if (x == y)`` and ``if (x = y)`` belies their drastically different
|
||||
semantics. While this can be mitigated with good tools, such tools would need
|
||||
to be deployed for Python, and even with perfect tooling, one-character bugs
|
||||
will still happen. Creating a new operator mitigates this, but creates a
|
||||
disconnect between regular assignment statements and these new assignment
|
||||
expressions, or would result in the old syntax being a short-hand usable in
|
||||
certain situations only.
|
||||
|
||||
7. Enhancing ``if`` and ``while`` syntax to permit the capture of their
|
||||
conditions::
|
||||
Regardless of the syntax, all of these have the problem that wide-scope names
|
||||
can be assigned to from an expression. This creates strange edge cases and
|
||||
unexpected behaviour, such as::
|
||||
|
||||
if re.search(pat, text) as match:
|
||||
print("Found:", match.group(0))
|
||||
# Name bindings inside list comprehensions usually won't leak
|
||||
x = [(y as local) for z in iter]
|
||||
# But occasionally they will!
|
||||
x = [y for z in (iter as leaky)]
|
||||
|
||||
This works beautifully if and ONLY if the desired condition is based on the
|
||||
truthiness of the captured value. It is thus effective for specific
|
||||
use-cases (regex matches, socket reads that return `''` when done), and
|
||||
completely useless in more complicated cases (eg where the condition is
|
||||
``f(x) < 0`` and you want to capture the value of ``f(x)``). It also has
|
||||
no benefit to list comprehensions.
|
||||
# Function default arguments are evaluated in the surrounding scope,
|
||||
# not the enclosing scope
|
||||
def x(y = (1 as z)):
|
||||
# z here is closing over the outer variable
|
||||
# z is a regular variable here
|
||||
|
||||
8. Adding a ``where:`` to any statement to create local name bindings::
|
||||
# Assignment targets are evaluated after the values to be assigned
|
||||
x[y] = f((1 as y))
|
||||
|
||||
value = x**2 + 2*x where:
|
||||
x = spam(1, 4, 7, q)
|
||||
|
||||
Execution order is inverted (the indented body is performed first, followed
|
||||
by the "header"). This requires a new keyword, unless an existing keyword
|
||||
is repurposed (most likely ``with:``).
|
||||
The same peculiarities can be seen with function calls and global/nonlocal
|
||||
declarations, but will become considerably more likely to occur.
|
||||
|
||||
|
||||
Discrepancies in the current implementation
|
||||
|
|
Loading…
Reference in New Issue