Recipe 18.9. Simulating the Ternary Operator in Python
Credit: Jürgen Hermann, Alex Martelli, Oliver
Steele, Chris Perkins, Brent Burley, Lloyd Goldwasser, Doug
Hudgeon
Problem
You want to
express in Python the equivalent of C's so-called
ternary operator ?:as in
condition?iftrue:iffalse).
Solution
There are many ways to skin a
ternary operator. An explicit
if/else is most Pythonic,
although slightly verbose:
for i in range(1, 3):Indexing is more compact, and therefore useful, if using the
if i == 1:
plural = ''
else:
plural = 's'
print "The loop ran %d time%s" % (i, plural)
iftrue and
iffalse expressions has no side effects:
for i in range(1, 3):For the specific case of plurals, there's also a
print "The loop ran %d time%s" % (i, ('', 's')[i != 1])
neat variant using slicing:
for i in range(1, 3):Short-circuited logical expressions can deal correctly with side
print "The loop ran %d time%s" % (i, "s"[i==1:])
effects:
for i in range(1, 3):The output of each of these loops is:
print "The loop ran %d time%s" % (i, i != 1 and 's' or '')
The loop ran 1 timeHowever, beware: the short-circuit version (which is necessary when
The loop ran 2 times
either or both of iftrue and
iffalse have side effects) fails if
"turned around":
for i in range(1, 3):Since '' evaluates as false, the would-be-ternary
print "The loop ran %d time%s" % (i, i == 1 and '' or 's')
expression always evaluates to 's', so that this
latest snippet outputs:
The loop ran 1 timesTherefore, in general, when iftrue and
The loop ran 2 times
iffalse are unknown at coding time (and
therefore either could have side effects or be false), we need more
cumbersome constructs, such as:
for i in range(1, 3):or:
print "The loop ran %d time%s" % (i, (i == 1 and [''] or ['s'])[0])
for i in range(1, 3):or even weirder variations:
print "The loop ran %d time%s" % (i, (lambda:'', lambda:'s')[i!=1]( ))
for i in range(1, 3):As you can see, good old
print "The loop ran %d time%s" % (i, [i==1 and '', i!=1 and 's'][i!=1])
for i in range(1, 3):
print "The loop ran %d time%s" % (i,
(i==1 and (lambda:'') or (lambda:'s'))( ))
if/else is starting to look
pretty good when compared to these terse and complicated approaches.And now for something completely different (for plurals only, again):
for i in range(1, 3):
print "The loop ran %d time%s" % (i, 's'*(i!=1))
Discussion
Programmers coming to Python from C, C++, or Perl sometimes miss the
so-called ternary operator (?:). The ternary
operator is most often used for avoiding a few lines of code and a
temporary variable for simple decisions, such as printing the plural
form of words after a counter, as in this recipe's
examples. In most cases, Python's preference for
making things clear and explicit at the cost of some conciseness is
an acceptable tradeoff, but one can sympathize with the withdrawal
symptoms of ternary-operator addicts.Nevertheless, 99.44 times out of 100, you're best
off using a plain if/else
statement. If you want your
if/else to fit in an expression
(so you can use that expression inside a lambda
form, list comprehension, or generator expression), put it inside a
named local function and use that function in the expression. For the
remaining 56 cases out of 10,000, the idioms in this recipe might be
useful. A typical case would be when you're
transliterating from another language into Python and need to keep
program structure as close as possible to the
"or"iginal, as mentioned in Recipe 4.19.There are several ways to get the ternary operator effect in Python,
and this recipe presents a fair selection of the wide range of
possibilities. Indexing and slicing are nice but
don't apply to cases in which either or both of the
iftrue and
iffalse expressions may have side effects.
If side effects are an issue, the short-circuiting nature of
and/or can help, but this
approach may run into trouble when iftrue
and iffalse might be Python values that
evaluate as false. To resolve both the side-effect and the
might-be-false issues, two variants in this recipe mix indexing and
function calling or a lambda form, and two more
use even weirder mixtures of lambda and indexing
and short circuiting.If you're not worried about side effects, you could
overload slicing syntax to express a ternary operator:
class cond(object):After executing this snippet, you could code the example presented in
def _ _getitem_ _(self, sl):
if sl.start: return sl.stop
else: return sl.step
cond = cond( )
this recipe's Solution as:
for i in range(1, 3):When you slice this cond object,
print "The loop ran %d time%s" % (i, cond[i==1:'':'s'])
iftrue and
iffalse (masquerading as the
stop and step attributes of the
slice object) are both evaluated in any case, which is the reason
this syntax is no use if you must worry about side effects. If you
must have syntax sugar, using nullary lambdas may
be the least of evils:
def cond(test, when_true, when_false):to be used as, for example, print cond(x%2==0, lambda:x//2,
if test:
return when_true( )
else:
return when_false( )
lambda:3*x+1).Note that the lack of a ternary operator in Python is not due to an
oversight: it's a deliberate design decision, made
after much debate pro and con. Therefore, you can be sure that Python
will never "grow" a ternary
operator. For many details about the various proposed syntax forms
for a ternary operation, you can see the rejected PEP 308 at
http://www.python.org/peps/pep-0308l.
See Also
Recipe 4.19.