252 lines
9.3 KiB
Plaintext
252 lines
9.3 KiB
Plaintext
PEP: 308
|
||
Title: If-then-else expression
|
||
Version: $Revision$
|
||
Last-Modified: $Date$
|
||
Author: Guido van Rossum, Raymond D. Hettinger
|
||
Status: Draft
|
||
Type: Standards Track
|
||
Content-Type: text/plain
|
||
Created: 7-Feb-2003
|
||
Post-History: 7-Feb-2003, 11-Feb-2003
|
||
|
||
|
||
Introduction
|
||
|
||
Requests for an if-then-else ("ternary") expression keep coming up
|
||
on comp.lang.python. This PEP contains a concrete proposal of a
|
||
fairly Pythonic syntax. This is the community's one chance: if
|
||
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
|
||
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
|
||
favor nor against this proposal; it is up to the community to
|
||
decide. If the community can't decide, the BDFL will reject the
|
||
PEP.
|
||
|
||
After unprecedented community response (very good arguments were
|
||
made both pro and con) this PEP has been revised with the help of
|
||
Raymond Hettinger. Without going through a complete revision
|
||
history, the main changes are a different proposed syntax, an
|
||
overview of proposed alternatives, the state of the curent
|
||
discussion, and a discussion of short-circuit behavior.
|
||
|
||
|
||
Proposal
|
||
|
||
The proposed syntax is as follows:
|
||
|
||
(if <condition>: <expression1> else: <expression2>)
|
||
|
||
Note that the enclosing parentheses are not optional.
|
||
|
||
The resulting expression is evaluated like this:
|
||
|
||
- First, <condition> is evaluated.
|
||
|
||
- If <condition> is true, <expression1> is evaluated and is the
|
||
result of the whole thing.
|
||
|
||
- If <condition> is false, <expression2> is evaluated and is the
|
||
result of the whole thing.
|
||
|
||
A natural extension of this syntax is to allow one or more 'elif'
|
||
parts:
|
||
|
||
(if <cond1>: <expr1> elif <cond2>: <expr2> ... else: <exprN>)
|
||
|
||
This will be implemented if the proposal is accepted.
|
||
|
||
The downsides to the proposal are:
|
||
|
||
* the required parentheses
|
||
* confusability with statement syntax
|
||
* additional semantic loading of colons
|
||
|
||
Note that at most one of <expression1> and <expression2> is
|
||
evaluated. This is called a "short-circuit expression"; it is
|
||
similar to the way the second operand of 'and' / 'or' is only
|
||
evaluated if the first operand is true / false.
|
||
|
||
A common way to emulate an if-then-else expression is:
|
||
|
||
<condition> and <expression1> or <expression2>
|
||
|
||
However, this doesn't work the same way: it returns <expression2>
|
||
when <expression1> is false! See FAQ 4.16 for alternatives that
|
||
work -- however, they are pretty ugly and require much more effort
|
||
to understand.
|
||
|
||
|
||
Alternatives
|
||
|
||
Holger Krekel proposed a new, minimally invasive variant:
|
||
|
||
<condition> and <expression1> else <expression2>
|
||
|
||
The concept behind it is that a nearly complete ternary operator
|
||
already exists with and/or and this proposal is the least invasive
|
||
change that makes it complete. Many respondants on the
|
||
newsgroup found this to be the most pleasing alternative.
|
||
However, a couple of respondants were able to post examples
|
||
that were mentally difficult to parse. Later it was pointed
|
||
out that this construct works by having the "else" change the
|
||
existing meaning of "and".
|
||
|
||
As a result, there is increasing support for Christian Tismer's
|
||
proposed variant of the same idea:
|
||
|
||
<condition> then <expression1> else <expression2>
|
||
|
||
The advantages are simple visual parsing, no required parenthesis,
|
||
no change in the semantics of existing keywords, not as likely
|
||
as the proposal to be confused with statement syntax, and does
|
||
not further overload the colon. The disadvantage is the
|
||
implementation costs of introducing a new keyword. However,
|
||
unlike other new keywords, the word "then" seems unlikely to
|
||
have been used as a name in existing programs.
|
||
|
||
---
|
||
|
||
Many C-derived languages use this syntax:
|
||
|
||
<condition> ? <expression1> : <expression2>
|
||
|
||
Eric Raymond even implemented this. The BDFL rejected this for
|
||
several reasons: the colon already has many uses in Python (even
|
||
though it would actually not be ambiguous, because the question
|
||
mark requires a matching colon); for people not used to C-derived
|
||
language, it is hard to understand.
|
||
|
||
---
|
||
|
||
The original version of this PEP proposed the following syntax:
|
||
|
||
<expression1> if <condition> else <expression2>
|
||
|
||
The out-of-order arrangement was found to be too uncomfortable
|
||
for many of participants in the discussion; especially when
|
||
<expression1> is long, it's easy to miss the conditional while
|
||
skimming.
|
||
|
||
---
|
||
|
||
Some have suggested adding a new builtin instead of extending the
|
||
syntax of the language. For example:
|
||
|
||
cond(<condition>, <expression1>, <expression2>)
|
||
|
||
This won't work the way a syntax extension will because both
|
||
expression1 and expression2 must be evaluated before the function
|
||
is called. There's no way to short-circuit the expression
|
||
evaluation. It could work if 'cond' (or some other name) were
|
||
made a keyword, but that has all the disadvantages of adding a new
|
||
keyword, plus confusing syntax: it *looks* like a function call so
|
||
a casual reader might expect both <expression1> and <expression2>
|
||
to be evaluated.
|
||
|
||
|
||
Summary of the Current State of the Discussion
|
||
|
||
Groups are falling into one of three camps:
|
||
|
||
1. Adopt a ternary operator built using punctuation characters:
|
||
|
||
<condition> ? <expression1> : <expression2>
|
||
|
||
2. Adopt a ternary operator built using new or existing keywords.
|
||
The leading examples are:
|
||
|
||
<condition> then <expression1> else <expression2>
|
||
(if <condition>: <expression1> else: <expression2>)
|
||
|
||
3. Do nothing.
|
||
|
||
The first two positions are relatively similar.
|
||
|
||
Some find that any form of punctuation makes the language more
|
||
cryptic. Others find that punctuation style is appropriate for
|
||
expressions rather than statements and helps avoid a COBOL style:
|
||
3 plus 4 times 5.
|
||
|
||
Adapting existing keywords attempts to improve on punctuation
|
||
through explicit meaning and a more tidy appearance. The downside
|
||
is some loss of the economy-of-expression provided by punctuation
|
||
operators. The other downside is that it creates some degree of
|
||
confusion between the two meanings and two usages of the keywords.
|
||
|
||
Those difficulties are overcome by options which introduce new
|
||
keywords which take more effort to implement.
|
||
|
||
The last position is doing nothing. Arguments in favor include
|
||
keeping the language simple and concise; maintaining backwards
|
||
compatibility; and that any every use case can already be already
|
||
expressed in terms of "if" and "else". Lambda expressions are an
|
||
exception as they require the conditional to be factored out into
|
||
a separate function definition.
|
||
|
||
The arguments against doing nothing are that the other choices
|
||
allow greater economy of expression and that current practices
|
||
show a propensity for erroneous uses of "and", "or", or one their
|
||
more complex, less visually unappealing workarounds.
|
||
|
||
|
||
Short-Circuit Behavior
|
||
|
||
The principal difference between the ternary operator and the
|
||
cond() function is that the latter provides an expression form but
|
||
does not provide short-circuit evaluation.
|
||
|
||
Short-circuit evaluation is desirable on three occasions:
|
||
|
||
1. When an expression has side-effects
|
||
2. When one or both of the expressions are resource intensive
|
||
3. When the condition serves as a guard for the validity of the
|
||
expression.
|
||
|
||
# Example where all three reasons apply
|
||
data = isinstance(source, file) ? source.readlines()
|
||
: source.split()
|
||
|
||
1. readlines() moves the file pointer
|
||
2. for long sources, both alternatives take time
|
||
3. split() is only valid for strings and readlines() is only
|
||
valid for file objects.
|
||
|
||
Supporters of a cond() function point out that the need for
|
||
short-circuit evaluation is rare. Scanning through existing code
|
||
directories, they found that if/else did not occur often; and of
|
||
those only a few contained expressions that could be helped by
|
||
cond() or a ternary operator; and that most of those had no need
|
||
for short-circuit evaluation. Hence, cond() would suffice for
|
||
most needs and would spare efforts to alter the syntax of the
|
||
language.
|
||
|
||
More supporting evidence comes from scans of C code bases which
|
||
show that its ternary operator used very rarely (as a percentage
|
||
of lines of code).
|
||
|
||
A counter point to that analysis is that the availability of a
|
||
ternary operator helped the programmer in every case because it
|
||
spared the need to search for side-effects. Further, it would
|
||
preclude errors arising from distant modifications which introduce
|
||
side-effects. The latter case has become more of a reality with
|
||
the advent of properties where even attribute access can be given
|
||
side-effects.
|
||
|
||
The BDFL's position is that short-circuit behavior is essential
|
||
for an if-then-else construct to be added to the language.
|
||
|
||
|
||
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
|
||
End:
|