Welcome, guest | Sign In | My Account | Store | Cart
2

Python's "if" is a _statement_, and there is no conditional _operator_ (like C's "a?b:c" ternary) that we could use where expressions are needed (lambdas, etc); however, with some due care, equivalents can easily be coded.

Python, 31 lines
 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
# with an if-else:
def foobar(whop):
    if whop>"pohrw": return "foox"
    else: return "barbar"

# the equivalent in expression-form:
foobar = lambda whop: ("foox", "barbar")[whop<="pohrw"]

# in general, we MUST ensure the 'conditional' turns
# into a 0 or 1 -- the 'not' operator is handy for that:
# not needed in the if-else case:
def plok(anything):
    if anything: return "xok"
    else: return "plik"

# but necessary when moving to expression-form:
plok = lambda anything: ("xok", "plik")[not anything]

# sometimes we need the shortcircuiting operators, 'and'
# and 'or', to avoid evaluating an incorrect expression:
def zod(plud):
    if plud: return 16+4/plud
    else: return 23

# must use and & or, NOT (...)[], as the operator-idiom:
zod = lambda plud: (plud and (16+4/plud)) or 23

# but if (16+4/plud)==0 [plud == -0.25] this erroneously
# returns 23!  A full solution ALSO requires indexing:
zod = lambda plud: ((plud and [16+4/plud]) or [23])[0]
# since the non-empty list [16+4/plud] is always 'true'

Python lacks a ternary operator like C's cond?iftrue:iffalse -- and generally it's all the better off for that; in the rare cases where it's needed (because 'if-else' is a _statement_, and we need something that fits into an _expression_ instead), we can synthesize it with indexing, or, when shortcircuiting is needed to avoid evaluating an incorrect expression, with the 'and' and 'or' operators.

The short-circuit case can be tricky indeed, because the 'iftrue' part could be 0 (or some other 'false' value); wrapping iftrue and iffalse up into one-element (thus non-empty, thus 'true') lists, and indexing the result, avoids this further risk of anomaly.

Such tricky subtleness can hardly ever meet the prime guideline of programming, "Do the Simplest Thing that Can Possibly Work", particularly because it's likely to be conjoined with 'lambda' or other subtlety-prone constructs. 99.44 times out of 100, you will be better off passing a named local function, with a plain if/else statement. But, for the remaining 56 cases out of 10,000, these idioms can still be useful.

11 comments

Lloyd Goldwasser 15 years, 2 months ago  # | flag

"conditionals" in expressions. Here's a slight variant that may or may not have a different appeal. As with the C version, a ? b : c, and the final version here, it evaluates only one of b and c, depending on the value of a, and then it picks that one, making no assumptions about its truth value:

[a and b, not a and c][not a]

Chris Perkins 15 years, 2 months ago  # | flag

this works too... a ? b : c <==> (a and (lambda:b) or (lambda:c))()

Doug Hudgeon 15 years ago  # | flag

sugar for a?b:c - cond[a:b:c]. Can also abuse slice for syntax that looks more suggestive of a?b:c

>>> cond[0:2:3]

3

>>> cond[1:2:3]

2

>

Where cond previously defined by:

class cond:
    __getitem__ = lambda self, sl: (sl.start and sl.stop,
        not sl.start and sl.step)[not sl.start]

cond=cond()

Could probably also do something like:

Q(a)[b:c]

In general __getitem__ and slice objects can be abused to define any ternary or quad(?) operations using [::] syntax.

Michael Chermside 14 years, 12 months ago  # | flag

Not short circuiting. Unfortunately, Doug, your solution is not short circuiting. So it really doesn't meet the requirements. I'll also throw out that this is exactly the kind of abuse seen in C++ which made me decide that I personally don't like operator overloading. It encourages programmers to invent their own strange syntax for their libraries.

Ashok Khosla 11 years, 8 months ago  # | flag

Newbie question - why can't I do this? [falseValue, trueValue][conditionIndex]. [falseValue, trueValue][conditionIndex]

should do the ternary operator - and it works like a switch as well... no?

Ashok Khosla 11 years, 8 months ago  # | flag

Newbie question - why can't I do this? [falseValue, trueValue][conditionIndex]. [falseValue, trueValue][conditionIndex]

should do the ternary operator - and it works like a switch as well... no?

or equivalents

{True: 'trueValue', False: 'falseValue'} [3>4]

('falseValue', 'trueValue')[conditionIndex]

Rockallite Wulf 10 years, 3 months ago  # | flag

Re: why can't I do this? [falseValue, trueValue][conditionIndex]. The problem is when you use [falseValue, trueValue] syntax, all items of the list are executed. For example:

li = [1, 2, 3, 4]
id = 1
re = [0, li.pop()][not id]
print re, li

It reads:

0 [1, 2, 3]

Another example of and-or:

li = [1, 2, 3, 4]
id = 1
re = (id and [0] or [li.pop()])[0]
print re, li

It reads:

0 [1, 2, 3, 4]

Thus it's different.

Dave Silvia 10 years, 3 months ago  # | flag

This appears to work everywhere. This needs a bit of defensive programming in cases where exceptions may arise (like division by zero), but, here it is, with examples:

#!/usr/bin/env python
#
#
#   dsilvia: 2006/06/25
#   Here's how it works.  It's a sequence that is indexed on the conditional.
#   In order to evaluate to a zero/non-zero, the 'not' of the conditional
#   is used, as python generates an error for conditionals that are non-numeric.
#

which=\
    lambda boolConditionTest,truthReturn,falseReturn:\
         (truthReturn,falseReturn)[not boolConditionTest]


if __name__ == '__main__':
    print "Syntax:"
    print "  which(bool conditional test,return if true,return if false)"
    print "Description:"
    print "  lambda function to approximate the C conditional operator"
    print ""

    theCond=''
    ifTrue='string'
    ifFalse='null string'
    print "which('"+theCond+"','"+ifTrue+"','"+ifFalse+"')"
    print "  returns '"+which(theCond,ifTrue,ifFalse)+"'"
    theCond='non-empty string'
    print "which('"+theCond+"','"+ifTrue+"','"+ifFalse+"')"
    print "  returns '"+which(theCond,ifTrue,ifFalse)+"'"
    theCond=[]
    ifTrue='list'
    ifFalse='no list'
    print "which(",theCond,",'"+ifTrue+"','"+ifFalse+"')"
    print "  returns '"+which(theCond,ifTrue,ifFalse)+"'"
    theCond=['one',2]
    print "which(",theCond,",'"+ifTrue+"','"+ifFalse+"')"
    print "  returns '"+which(theCond,ifTrue,ifFalse)+"'"
    theCond=0 == 1
    ifTrue='true'
    ifFalse='false'
    print "which(",theCond,",'"+ifTrue+"','"+ifFalse+"')"
    print "  returns '"+which(theCond,ifTrue,ifFalse)+"'"
    theCond=1 == 1
    print "which(",theCond,",'"+ifTrue+"','"+ifFalse+"')"
    print "  returns '"+which(theCond,ifTrue,ifFalse)+"'"
    theCond=0
    ifTrue=1
    ifFalse=0
    print "which(",theCond,",",ifTrue,",",ifFalse,")"
    print "  returns ",which(theCond,ifTrue,ifFalse)
    theCond=1
    print "which(",theCond,",",ifTrue,",",ifFalse,")"
    print "  returns ",which(theCond,ifTrue,ifFalse)
# Note: if division by zero is possible in your test, you need to nest
#       a call to which into the expression where this is possible to
#       avoid an exception.  There may be similar cases in other instances.
    theCond=-.25
    ifTrue=16+4/which(theCond,theCond,1)
    ifFalse=23
    print "which(",theCond,",",ifTrue,"(*) ,",ifFalse,")"
    print "  returns",which(theCond,ifTrue,ifFalse)
    theCond=0
    ifTrue=16+4/which(theCond,theCond,1)
    print "which(",theCond,",",ifTrue,"(**) ,",ifFalse,")"
    print "  returns",which(theCond,ifTrue,ifFalse)
    theCond=-.25
    print "(*)  16+4/which(",theCond,",",theCond,", 1 ) ==",\
          which(theCond,theCond,1),\
          "->",16+4/which(theCond,theCond,1)
    theCond=0
    print "(**) 16+4/which(",theCond,",",theCond,", 1 ) ==",\

(comment continued...)

Dave Silvia 10 years, 3 months ago  # | flag

(...continued from previous comment)

          which(theCond,theCond,1),\
          "->",16+4/which(theCond,theCond,1)



And the output:
<pre>
Syntax:
  which(bool conditional test,return if true,return if false)
Description:
  lambda function to approximate the C conditional operator

which('','string','null string')
  returns 'null string'
which('non-empty string','string','null string')
  returns 'string'
which( [] ,'list','no list')
  returns 'no list'
which( ['one', 2] ,'list','no list')
  returns 'list'
which( False ,'true','false')
  returns 'false'
which( True ,'true','false')
  returns 'true'
which( 0 , 1 , 0 )
  returns  0
which( 1 , 1 , 0 )
  returns  1
which( -0.25 , 0.0 (*) , 23 )
  returns 0.0
which( 0 , 20 (**) , 23 )
  returns 23
(*)  16+4/which( -0.25 , -0.25 , 1 ) == -0.25 -> 0.0
(**) 16+4/which( 0 , 0 , 1 ) == 1 -> 20

</pre>

Archimerged Submedes 9 years, 3 months ago  # | flag

The above which(a,b,c) evaluates all arguments.

>>> li=[1,2,3,4]
>>> which(0,li.pop(),li)
[1, 2, 3]
>>> which(0,li.pop(),li)
[1, 2]
>>> which(0,li.pop(),li)
[1]
>>> which(0,li.pop(),li)
[]
>>> which(0,li.pop(),li)
Traceback (most recent call last):
  File "", line 1, in ?
IndexError: pop from empty list
>>>
Archimerged Submedes 9 years, 3 months ago  # | flag

Add a comment

Sign in to comment

Created by Alex Martelli on Mon, 26 Mar 2001 (PSF)
Python recipes (4485)
Alex Martelli's recipes (27)

Required Modules

  • (none specified)

Other Information and Tasks