reSTify PEP 308 (#423)

This commit is contained in:
Huang Huang 2017-10-24 14:24:48 -05:00 committed by Brett Cannon
parent 370be4c92a
commit 5a15c92dcf
1 changed files with 239 additions and 218 deletions

View File

@ -5,30 +5,33 @@ Last-Modified: $Date$
Author: Guido van Rossum, Raymond Hettinger Author: Guido van Rossum, Raymond Hettinger
Status: Final Status: Final
Type: Standards Track Type: Standards Track
Content-Type: text/plain Content-Type: text/x-rst
Created: 7-Feb-2003 Created: 7-Feb-2003
Post-History: 7-Feb-2003, 11-Feb-2003 Post-History: 7-Feb-2003, 11-Feb-2003
Adding a conditional expression Adding a conditional expression
===============================
On 9/29/2005, Guido decided to add conditional expressions in the On 9/29/2005, Guido decided to add conditional expressions in the
form of "X if C else Y". [1] form of "X if C else Y". [1]_
The motivating use case was the prevalence of error-prone attempts The motivating use case was the prevalence of error-prone attempts
to achieve the same effect using "and" and "or". [2] to achieve the same effect using "and" and "or". [2]_
Previous community efforts to add a conditional expression were Previous community efforts to add a conditional expression were
stymied by a lack of consensus on the best syntax. That issue was stymied by a lack of consensus on the best syntax. That issue was
resolved by simply deferring to a BDFL best judgment call. resolved by simply deferring to a BDFL best judgment call.
The decision was validated by reviewing how the syntax fared when The decision was validated by reviewing how the syntax fared when
applied throughout the standard library (this review approximates a applied throughout the standard library (this review approximates a
sampling of real-world use cases, across a variety of applications, sampling of real-world use cases, across a variety of applications,
written by a number of programmers with diverse backgrounds). [3] written by a number of programmers with diverse backgrounds). [3]_
The following change will be made to the grammar. (The or_test The following change will be made to the grammar. (The or_test
symbols is new, the others are modified.) symbols is new, the others are modified.)
::
test: or_test ['if' or_test 'else' test] | lambdef test: or_test ['if' or_test 'else' test] | lambdef
or_test: and_test ('or' and_test)* or_test: and_test ('or' and_test)*
@ -37,29 +40,29 @@ Adding a conditional expression
... ...
gen_for: 'for' exprlist 'in' or_test [gen_iter] gen_for: 'for' exprlist 'in' or_test [gen_iter]
The new syntax nearly introduced a minor syntactical backwards The new syntax nearly introduced a minor syntactical backwards
incompatibility. In previous Python versions, the following is incompatibility. In previous Python versions, the following is
legal: legal::
[f for f in lambda x: x, lambda x: x**2 if f(1) == 1] [f for f in lambda x: x, lambda x: x**2 if f(1) == 1]
(I.e. a list comprehension where the sequence following 'in' is an (I.e. a list comprehension where the sequence following 'in' is an
unparenthesized series of lambdas -- or just one lambda, even.) unparenthesized series of lambdas -- or just one lambda, even.)
In Python 3.0, the series of lambdas will have to be In Python 3.0, the series of lambdas will have to be
parenthesized, e.g.: parenthesized, e.g.::
[f for f in (lambda x: x, lambda x: x**2) if f(1) == 1] [f for f in (lambda x: x, lambda x: x**2) if f(1) == 1]
This is because lambda binds less tight than the if-else This is because lambda binds less tight than the if-else
expression, but in this context, the lambda could already be expression, but in this context, the lambda could already be
followed by an 'if' keyword that binds less tightly still (for followed by an 'if' keyword that binds less tightly still (for
details, consider the grammar changes shown above). details, consider the grammar changes shown above).
However, in Python 2.5, a slightly different grammar is used that However, in Python 2.5, a slightly different grammar is used that
is more backwards compatible, but constrains the grammar of a is more backwards compatible, but constrains the grammar of a
lambda used in this position by forbidding the lambda's body to lambda used in this position by forbidding the lambda's body to
contain an unparenthesized condition expression. Examples: contain an unparenthesized condition expression. Examples::
[f for f in (1, lambda x: x if x >= 0 else -1)] # OK [f for f in (1, lambda x: x if x >= 0 else -1)] # OK
[f for f in 1, (lambda x: x if x >= 0 else -1)] # OK [f for f in 1, (lambda x: x if x >= 0 else -1)] # OK
@ -68,255 +71,266 @@ Adding a conditional expression
References References
==========
[1] Pronouncement .. [1] Pronouncement
https://mail.python.org/pipermail/python-dev/2005-September/056846.html https://mail.python.org/pipermail/python-dev/2005-September/056846.html
[2] Motivating use case: .. [2] Motivating use case:
https://mail.python.org/pipermail/python-dev/2005-September/056546.html https://mail.python.org/pipermail/python-dev/2005-September/056546.html
https://mail.python.org/pipermail/python-dev/2005-September/056510.html https://mail.python.org/pipermail/python-dev/2005-September/056510.html
[3] Review in the context of real-world code fragments: .. [3] Review in the context of real-world code fragments:
https://mail.python.org/pipermail/python-dev/2005-September/056803.html https://mail.python.org/pipermail/python-dev/2005-September/056803.html
Introduction to earlier draft of the PEP (kept for historical purposes) Introduction to earlier draft of the PEP (kept for historical purposes)
=======================================================================
Requests for an if-then-else ("ternary") expression keep coming up Requests for an if-then-else ("ternary") expression keep coming up
on comp.lang.python. This PEP contains a concrete proposal of a on comp.lang.python. This PEP contains a concrete proposal of a
fairly Pythonic syntax. This is the community's one chance: if fairly Pythonic syntax. This is the community's one chance: if
this PEP is approved with a clear majority, it will be implemented this PEP is approved with a clear majority, it will be implemented
in Python 2.4. If not, the PEP will be augmented with a summary in Python 2.4. If not, the PEP will be augmented with a summary
of the reasons for rejection and the subject better not come up of the reasons for rejection and the subject better not come up
again. While the BDFL is co-author of this PEP, he is neither in again. While the BDFL is co-author of this PEP, he is neither in
favor nor against this proposal; it is up to the community to favor nor against this proposal; it is up to the community to
decide. If the community can't decide, the BDFL will reject the decide. If the community can't decide, the BDFL will reject the
PEP. PEP.
After unprecedented community response (very good arguments were After unprecedented community response (very good arguments were
made both pro and con) this PEP has been revised with the help of made both pro and con) this PEP has been revised with the help of
Raymond Hettinger. Without going through a complete revision Raymond Hettinger. Without going through a complete revision
history, the main changes are a different proposed syntax, an history, the main changes are a different proposed syntax, an
overview of proposed alternatives, the state of the curent overview of proposed alternatives, the state of the curent
discussion, and a discussion of short-circuit behavior. discussion, and a discussion of short-circuit behavior.
Following the discussion, a vote was held. While there was an overall Following the discussion, a vote was held. While there was an overall
interest in having some form of if-then-else expressions, no one interest in having some form of if-then-else expressions, no one
format was able to draw majority support. Accordingly, the PEP was format was able to draw majority support. Accordingly, the PEP was
rejected due to the lack of an overwhelming majority for change. rejected due to the lack of an overwhelming majority for change.
Also, a Python design principle has been to prefer the status quo Also, a Python design principle has been to prefer the status quo
whenever there are doubts about which path to take. whenever there are doubts about which path to take.
Proposal Proposal
========
The proposed syntax is as follows: The proposed syntax is as follows::
(if <condition>: <expression1> else: <expression2>) (if <condition>: <expression1> else: <expression2>)
Note that the enclosing parentheses are not optional. Note that the enclosing parentheses are not optional.
The resulting expression is evaluated like this: The resulting expression is evaluated like this:
- First, <condition> is evaluated. - First, <condition> is evaluated.
- If <condition> is true, <expression1> is evaluated and is the - If <condition> is true, <expression1> is evaluated and is the
result of the whole thing. result of the whole thing.
- If <condition> is false, <expression2> is evaluated and is the - If <condition> is false, <expression2> is evaluated and is the
result of the whole thing. result of the whole thing.
A natural extension of this syntax is to allow one or more 'elif' A natural extension of this syntax is to allow one or more 'elif'
parts: parts::
(if <cond1>: <expr1> elif <cond2>: <expr2> ... else: <exprN>) (if <cond1>: <expr1> elif <cond2>: <expr2> ... else: <exprN>)
This will be implemented if the proposal is accepted. This will be implemented if the proposal is accepted.
The downsides to the proposal are: The downsides to the proposal are:
* the required parentheses * the required parentheses
* confusability with statement syntax * confusability with statement syntax
* additional semantic loading of colons * additional semantic loading of colons
Note that at most one of <expression1> and <expression2> is Note that at most one of <expression1> and <expression2> is
evaluated. This is called a "short-circuit expression"; it is evaluated. This is called a "short-circuit expression"; it is
similar to the way the second operand of 'and' / 'or' is only similar to the way the second operand of 'and' / 'or' is only
evaluated if the first operand is true / false. evaluated if the first operand is true / false.
A common way to emulate an if-then-else expression is: A common way to emulate an if-then-else expression is::
<condition> and <expression1> or <expression2> <condition> and <expression1> or <expression2>
However, this doesn't work the same way: it returns <expression2> However, this doesn't work the same way: it returns <expression2>
when <expression1> is false! See FAQ 4.16 for alternatives that when <expression1> is false! See FAQ 4.16 for alternatives that
work -- however, they are pretty ugly and require much more effort work -- however, they are pretty ugly and require much more effort
to understand. to understand.
Alternatives Alternatives
============
Holger Krekel proposed a new, minimally invasive variant: Holger Krekel proposed a new, minimally invasive variant::
<condition> and <expression1> else <expression2> <condition> and <expression1> else <expression2>
The concept behind it is that a nearly complete ternary operator The concept behind it is that a nearly complete ternary operator
already exists with and/or and this proposal is the least invasive already exists with and/or and this proposal is the least invasive
change that makes it complete. Many respondants on the change that makes it complete. Many respondants on the
newsgroup found this to be the most pleasing alternative. newsgroup found this to be the most pleasing alternative.
However, a couple of respondants were able to post examples However, a couple of respondants were able to post examples
that were mentally difficult to parse. Later it was pointed that were mentally difficult to parse. Later it was pointed
out that this construct works by having the "else" change the out that this construct works by having the "else" change the
existing meaning of "and". existing meaning of "and".
As a result, there is increasing support for Christian Tismer's As a result, there is increasing support for Christian Tismer's
proposed variant of the same idea: proposed variant of the same idea::
<condition> then <expression1> else <expression2> <condition> then <expression1> else <expression2>
The advantages are simple visual parsing, no required parentheses, The advantages are simple visual parsing, no required parentheses,
no change in the semantics of existing keywords, not as likely no change in the semantics of existing keywords, not as likely
as the proposal to be confused with statement syntax, and does as the proposal to be confused with statement syntax, and does
not further overload the colon. The disadvantage is the not further overload the colon. The disadvantage is the
implementation costs of introducing a new keyword. However, implementation costs of introducing a new keyword. However,
unlike other new keywords, the word "then" seems unlikely to unlike other new keywords, the word "then" seems unlikely to
have been used as a name in existing programs. have been used as a name in existing programs.
--- ---
Many C-derived languages use this syntax: Many C-derived languages use this syntax::
<condition> ? <expression1> : <expression2> <condition> ? <expression1> : <expression2>
Eric Raymond even implemented this. The BDFL rejected this for Eric Raymond even implemented this. The BDFL rejected this for
several reasons: the colon already has many uses in Python (even several reasons: the colon already has many uses in Python (even
though it would actually not be ambiguous, because the question though it would actually not be ambiguous, because the question
mark requires a matching colon); for people not used to C-derived mark requires a matching colon); for people not used to C-derived
language, it is hard to understand. language, it is hard to understand.
--- ---
The original version of this PEP proposed the following syntax: The original version of this PEP proposed the following syntax::
<expression1> if <condition> else <expression2> <expression1> if <condition> else <expression2>
The out-of-order arrangement was found to be too uncomfortable The out-of-order arrangement was found to be too uncomfortable
for many of participants in the discussion; especially when for many of participants in the discussion; especially when
<expression1> is long, it's easy to miss the conditional while <expression1> is long, it's easy to miss the conditional while
skimming. skimming.
--- ---
Some have suggested adding a new builtin instead of extending the Some have suggested adding a new builtin instead of extending the
syntax of the language. For example: syntax of the language. For example::
cond(<condition>, <expression1>, <expression2>) cond(<condition>, <expression1>, <expression2>)
This won't work the way a syntax extension will because both This won't work the way a syntax extension will because both
expression1 and expression2 must be evaluated before the function expression1 and expression2 must be evaluated before the function
is called. There's no way to short-circuit the expression is called. There's no way to short-circuit the expression
evaluation. It could work if 'cond' (or some other name) were evaluation. It could work if 'cond' (or some other name) were
made a keyword, but that has all the disadvantages of adding a new made a keyword, but that has all the disadvantages of adding a new
keyword, plus confusing syntax: it *looks* like a function call so keyword, plus confusing syntax: it **looks** like a function call so
a casual reader might expect both <expression1> and <expression2> a casual reader might expect both <expression1> and <expression2>
to be evaluated. to be evaluated.
Summary of the Current State of the Discussion Summary of the Current State of the Discussion
==============================================
Groups are falling into one of three camps: Groups are falling into one of three camps:
1. Adopt a ternary operator built using punctuation characters: 1. Adopt a ternary operator built using punctuation characters::
<condition> ? <expression1> : <expression2> <condition> ? <expression1> : <expression2>
2. Adopt a ternary operator built using new or existing keywords. 2. Adopt a ternary operator built using new or existing keywords.
The leading examples are: The leading examples are::
<condition> then <expression1> else <expression2> <condition> then <expression1> else <expression2>
(if <condition>: <expression1> else: <expression2>) (if <condition>: <expression1> else: <expression2>)
3. Do nothing. 3. Do nothing.
The first two positions are relatively similar. The first two positions are relatively similar.
Some find that any form of punctuation makes the language more Some find that any form of punctuation makes the language more
cryptic. Others find that punctuation style is appropriate for cryptic. Others find that punctuation style is appropriate for
expressions rather than statements and helps avoid a COBOL style: expressions rather than statements and helps avoid a COBOL style:
3 plus 4 times 5. 3 plus 4 times 5.
Adapting existing keywords attempts to improve on punctuation Adapting existing keywords attempts to improve on punctuation
through explicit meaning and a more tidy appearance. The downside through explicit meaning and a more tidy appearance. The downside
is some loss of the economy-of-expression provided by punctuation is some loss of the economy-of-expression provided by punctuation
operators. The other downside is that it creates some degree of operators. The other downside is that it creates some degree of
confusion between the two meanings and two usages of the keywords. confusion between the two meanings and two usages of the keywords.
Those difficulties are overcome by options which introduce new Those difficulties are overcome by options which introduce new
keywords which take more effort to implement. keywords which take more effort to implement.
The last position is doing nothing. Arguments in favor include The last position is doing nothing. Arguments in favor include
keeping the language simple and concise; maintaining backwards keeping the language simple and concise; maintaining backwards
compatibility; and that any every use case can already be already compatibility; and that any every use case can already be already
expressed in terms of "if" and "else". Lambda expressions are an expressed in terms of "if" and "else". Lambda expressions are an
exception as they require the conditional to be factored out into exception as they require the conditional to be factored out into
a separate function definition. a separate function definition.
The arguments against doing nothing are that the other choices The arguments against doing nothing are that the other choices
allow greater economy of expression and that current practices allow greater economy of expression and that current practices
show a propensity for erroneous uses of "and", "or", or one their show a propensity for erroneous uses of "and", "or", or one their
more complex, less visually unappealing workarounds. more complex, less visually unappealing workarounds.
Short-Circuit Behavior Short-Circuit Behavior
======================
The principal difference between the ternary operator and the The principal difference between the ternary operator and the
cond() function is that the latter provides an expression form but ``cond()`` function is that the latter provides an expression form but
does not provide short-circuit evaluation. does not provide short-circuit evaluation.
Short-circuit evaluation is desirable on three occasions: Short-circuit evaluation is desirable on three occasions:
1. When an expression has side-effects 1. When an expression has side-effects
2. When one or both of the expressions are resource intensive 2. When one or both of the expressions are resource intensive
3. When the condition serves as a guard for the validity of the 3. When the condition serves as a guard for the validity of the
expression. expression.
::
# Example where all three reasons apply # Example where all three reasons apply
data = isinstance(source, file) ? source.readlines() data = isinstance(source, file) ? source.readlines()
: source.split() : source.split()
1. readlines() moves the file pointer 1. ``readlines()`` moves the file pointer
2. for long sources, both alternatives take time 2. for long sources, both alternatives take time
3. split() is only valid for strings and readlines() is only 3. ``split()`` is only valid for strings and ``readlines()`` is only
valid for file objects. valid for file objects.
Supporters of a cond() function point out that the need for Supporters of a ``cond()`` function point out that the need for
short-circuit evaluation is rare. Scanning through existing code short-circuit evaluation is rare. Scanning through existing code
directories, they found that if/else did not occur often; and of directories, they found that if/else did not occur often; and of
those only a few contained expressions that could be helped by those only a few contained expressions that could be helped by
cond() or a ternary operator; and that most of those had no need ``cond()`` or a ternary operator; and that most of those had no need
for short-circuit evaluation. Hence, cond() would suffice for for short-circuit evaluation. Hence, ``cond()`` would suffice for
most needs and would spare efforts to alter the syntax of the most needs and would spare efforts to alter the syntax of the
language. language.
More supporting evidence comes from scans of C code bases which More supporting evidence comes from scans of C code bases which
show that its ternary operator used very rarely (as a percentage show that its ternary operator used very rarely (as a percentage
of lines of code). of lines of code).
A counterpoint to that analysis is that the availability of a A counterpoint to that analysis is that the availability of a
ternary operator helped the programmer in every case because it ternary operator helped the programmer in every case because it
spared the need to search for side-effects. Further, it would spared the need to search for side-effects. Further, it would
preclude errors arising from distant modifications which introduce preclude errors arising from distant modifications which introduce
side-effects. The latter case has become more of a reality with side-effects. The latter case has become more of a reality with
the advent of properties where even attribute access can be given the advent of properties where even attribute access can be given
side-effects. side-effects.
The BDFL's position is that short-circuit behavior is essential The BDFL's position is that short-circuit behavior is essential
for an if-then-else construct to be added to the language. for an if-then-else construct to be added to the language.
Detailed Results of Voting Detailed Results of Voting
==========================
::
Votes rejecting all options: 82 Votes rejecting all options: 82
Votes with rank ordering: 436 Votes with rank ordering: 436
--- ---
@ -352,8 +366,11 @@ Detailed Results of Voting
Total 363 286 202 155 231 312 1549 Total 363 286 202 155 231 312 1549
CHOICE KEY CHOICE KEY
---------- ----------
::
A. x if C else y A. x if C else y
B. if C then x else y B. if C then x else y
C. (if C: x else: y) C. (if C: x else: y)
@ -373,8 +390,11 @@ Detailed Results of Voting
Q. any write-in vote Q. any write-in vote
Detail for write-in votes and their ranking: Detail for write-in votes and their ranking
-------------------------------------------- --------------------------------------------
::
3: Q reject y x C elsethenif 3: Q reject y x C elsethenif
2: Q accept (C ? x ! y) 2: Q accept (C ? x ! y)
3: Q reject ... 3: Q reject ...
@ -440,14 +460,15 @@ Detailed Results of Voting
Copyright Copyright
=========
This document has been placed in the public domain. This document has been placed in the public domain.
..
Local Variables: Local Variables:
mode: indented-text mode: indented-text
indent-tabs-mode: nil indent-tabs-mode: nil
sentence-end-double-space: t sentence-end-double-space: t
fill-column: 70 fill-column: 70
End: End: