Source

python-peps / pep-0308.txt

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
PEP: 308
Title: Conditional Expressions
Version: $Revision$
Last-Modified: $Date$
Author: Guido van Rossum, Raymond Hettinger
Status: Final
Type: Standards Track
Content-Type: text/plain
Created: 7-Feb-2003
Post-History: 7-Feb-2003, 11-Feb-2003


Adding a conditional expression

    On 9/29/2005, Guido decided to add conditional expressions in the
    form of "X if C else Y". [1]

    The motivating use case was the prevalance of error-prone attempts
    to achieve the same effect using "and" and "or". [2]
    
    Previous community efforts to add a conditional expression were
    stymied by a lack of consensus on the best syntax.  That issue was
    resolved by simply deferring to a BDFL best judgment call.

    The decision was validated by reviewing how the syntax fared when
    applied throughout the standard library (this review approximates a
    sampling of real-world use cases, across a variety of applications,
    written by a number of programmers with diverse backgrounds). [3]

    The following change will be made to the grammar.  (The or_test
    symbols is new, the others are modified.)

        test: or_test ['if' or_test 'else' test] | lambdef
        or_test: and_test ('or' and_test)*
        ...
        testlist_safe: or_test [(',' or_test)+ [',']]
        ...
        gen_for: 'for' exprlist 'in' or_test [gen_iter]

    The new syntax nearly introduced a minor syntactical backwards
    incompatibility.  In previous Python versions, the following is
    legal:

        [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
    unparenthesized series of lambdas -- or just one lambda, even.)

    In Python 3.0, the series of lambdas will have to be
    parenthesized, e.g.:

        [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
    expression, but in this context, the lambda could already be
    followed by an 'if' keyword that binds less tightly still (for
    details, consider the grammar changes shown above).

    However, in Python 2.5, a slightly different grammar is used that
    is more backwards compatible, but constrains the grammar of a
    lambda used in this position by forbidding the lambda's body to
    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]      # INVALID


References

    [1] Pronouncement
        http://mail.python.org/pipermail/python-dev/2005-September/056846.html

    [2] Motivating use case:
        http://mail.python.org/pipermail/python-dev/2005-September/056546.html
        http://mail.python.org/pipermail/python-dev/2005-September/056510.html        

    [3] Review in the context of real-world code fragments:
        http://mail.python.org/pipermail/python-dev/2005-September/056803.html


Introduction to earlier draft of the PEP (kept for historical purposes)

    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.

    Following the discussion, a vote was held.  While there was an overall
    interest in having some form of if-then-else expressions, no one
    format was able to draw majority support.  Accordingly, the PEP was
    rejected due to the lack of an overwhelming majority for change.
    Also, a Python design principle has been to prefer the status quo
    whenever there are doubts about which path to take.


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.


Detailed Results of Voting


    Votes rejecting all options:  82
    Votes with rank ordering:     436
                                  ---
    Total votes received:         518


            ACCEPT                  REJECT                  TOTAL
            ---------------------   ---------------------   -----
            Rank1   Rank2   Rank3   Rank1   Rank2   Rank3
    Letter  
    A       51      33      19      18      20      20      161
    B       45      46      21      9       24      23      168
    C       94      54      29      20      20      18      235
    D       71      40      31      5       28      31      206
    E       7       7       10              3       5       32
    F       14      19      10              7       17      67
    G       7       6       10      1       2       4       30
    H       20      22      17      4       10      25      98
    I       16      20      9       5       5       20      75
    J       6       17      5       1               10      39
    K       1               6               4       13      24
    L               1       2               3       3       9
    M       7       3       4       2       5       11      32
    N               2       3               4       2       11
    O       1       6       5       1       4       9       26
    P       5       3       6       1       5       7       27
    Q       18      7       15      6       5       11      62
    Z                                               1       1
            ---     ---     ---     ---     ---     ---     ----
    Total   363     286     202     73      149     230     1303
    RejectAll                       82      82      82      246
            ---     ---     ---     ---     ---     ---     ----
    Total   363     286     202     155     231     312     1549


    CHOICE KEY
    ----------
    A.  x if C else y
    B.  if C then x else y
    C.  (if C: x else: y)
    D.  C ? x : y
    E.  C ? x ! y
    F.  cond(C, x, y)
    G.  C ?? x || y
    H.  C then x else y
    I.  x when C else y
    J.  C ? x else y
    K.  C -> x else y
    L.  C -> (x, y)
    M.  [x if C else y]
    N.  ifelse C: x else y
    O.  <if C then x else y>
    P.  C and x else y
    Q.  any write-in vote


    Detail for write-in votes and their ranking:
    --------------------------------------------
    3:  Q reject y x C elsethenif
    2:  Q accept (C ? x ! y)
    3:  Q reject ...
    3:  Q accept  ? C : x : y
    3:  Q accept (x if C, y otherwise)
    3:  Q reject ...
    3:  Q reject NONE
    1:  Q accept   select : (<c1> : <val1>; [<cx> : <valx>; ]* elseval)
    2:  Q reject if C: t else: f
    3:  Q accept C selects x else y
    2:  Q accept iff(C, x, y)    # "if-function"
    1:  Q accept (y, x)[C]
    1:  Q accept          C true: x false: y
    3:  Q accept          C then: x else: y
    3:  Q reject
    3:  Q accept (if C: x elif C2: y else: z)
    3:  Q accept C -> x : y
    1:  Q accept  x (if C), y
    1:  Q accept if c: x else: y
    3:  Q accept (c).{True:1, False:2}
    2:  Q accept if c: x else: y
    3:  Q accept (c).{True:1, False:2}
    3:  Q accept if C: x else y
    1:  Q accept  (x if C else y)
    1:  Q accept ifelse(C, x, y)
    2:  Q reject x or y <- C
    1:  Q accept (C ? x : y) required parens
    1:  Q accept  iif(C, x, y)
    1:  Q accept ?(C, x, y)
    1:  Q accept switch-case
    2:  Q accept multi-line if/else
    1:  Q accept C: x else: y
    2:  Q accept (C): x else: y
    3:  Q accept if C: x else: y
    1:  Q accept     x if C, else y
    1:  Q reject choice: c1->a; c2->b; ...; z
    3:  Q accept [if C then x else y]
    3:  Q reject no other choice has x as the first element
    1:  Q accept (x,y) ? C
    3:  Q accept x if C else y (The "else y" being optional)
    1:  Q accept (C ? x , y)
    1:  Q accept  any outcome (i.e form or plain rejection) from a usability study
    1:  Q reject (x if C else y)
    1:  Q accept  (x if C else y)
    2:  Q reject   NONE
    3:  Q reject   NONE
    3:  Q accept  (C ? x else y)
    3:  Q accept  x when C else y
    2:  Q accept  (x if C else y)
    2:  Q accept cond(C1, x1, C2, x2, C3, x3,...)
    1:  Q accept  (if C1: x elif C2: y else: z)
    1:  Q reject cond(C, :x, :y)
    3:  Q accept  (C and [x] or [y])[0]
    2:  Q reject
    3:  Q reject
    3:  Q reject all else
    1:  Q reject no-change
    3:  Q reject deliberately omitted as I have no interest in any other proposal
    2:  Q reject (C then x else Y)
    1:  Q accept       if C: x else: y
    1:  Q reject (if C then x else y)
    3:  Q reject C?(x, y)


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:
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.