Recipe18.9.Simulating the Ternary Operator in Python


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):     if i == 1:         plural = ''     else:         plural = 's'     print "The loop ran %d time%s" % (i, plural)

Indexing is more compact, and therefore useful, if using the iftrue and iffalse expressions has no side effects:

for i in range(1, 3):     print "The loop ran %d time%s" % (i, ('', 's')[i != 1])

For the specific case of plurals, there's also a neat variant using slicing:

for i in range(1, 3):     print "The loop ran %d time%s" % (i, "s"[i==1:])

Short-circuited logical expressions can deal correctly with side effects:

for i in range(1, 3):     print "The loop ran %d time%s" % (i, i != 1 and 's' or '')

The output of each of these loops is:

The loop ran 1 time The loop ran 2 times

However, beware: the short-circuit version (which is necessary when either or both of iftrue and iffalse have side effects) fails if "turned around":

for i in range(1, 3):     print "The loop ran %d time%s" % (i, i == 1 and '' or 's')

Since '' evaluates as false, the would-be-ternary expression always evaluates to 's', so that this latest snippet outputs:

The loop ran 1 times The loop ran 2 times

Therefore, in general, when iftrue and 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):     print "The loop ran %d time%s" % (i, (i == 1 and [''] or ['s'])[0])

or:

for i in range(1, 3):     print "The loop ran %d time%s" % (i, (lambda:'', lambda:'s')[i!=1]( ))

or even weirder variations:

for i in range(1, 3):     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'))( ))

As you can see, good old 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):     def _ _getitem_ _(self, sl):         if sl.start: return sl.stop         else: return sl.step cond = cond( )

After executing this snippet, you could code the example presented in this recipe's Solution as:

for i in range(1, 3):     print "The loop ran %d time%s" % (i, cond[i==1:'':'s'])

When you slice this cond object, 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):     if test:         return when_true( )     else:         return when_false( )

to be used as, for example, print cond(x%2==0, lambda:x//2, 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-0308.html.

See Also

Recipe 4.19.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net