4

I don't understand this syntax.

Python program to demonstrate ternary operator

a, b = 10, 20

Use tuple for selecting an item

print( (b, a) [a < b] )

Use Dictionary for selecting an item

print({True: a, False: b} [a < b])

PS: I guess this is from older version of Python, because in newer versions (don't know from which version) True and False are reserved keywords, so they can't be assigned a value.

lamda is more efficient than above two methods because in lambda we are assure that only one expression will be evaluated unlike in tuple and Dictionary

print((lambda: b, lambda: a)[a < b]())

Syntax should be:

[on_true] if [expression] else [on_false]

So how does

print( (b, a) [a < b] )
print({True: a, False: b} [a < b])
print((lambda: b, lambda: a)[a < b]())

fit this syntax? What's the meaning of [a<b] after tuple/dictionary/lambda? I have never seen this syntax before. It also works when list [b, a] precedes [a<b].

I would expect it to look like this

print( a if a < b else b )

Link to resource: https://www.geeksforgeeks.org/ternary-operator-in-python/

boldi
  • 83
  • 1
  • 6
  • 1
    `True` and `False` aren't being assigned values; they are just being used as the keys of a `dict`. – chepner Sep 05 '18 at 13:49
  • That's true. I haven't realized it's just a key not assignment. – boldi Sep 05 '18 at 13:51
  • Those are alternative syntax instead of `[on_true] if [expression] else [on_false]`. They use the fact that `foo = {1: "one"}` `print( foo[1] )` can be written directly as `print( {1: "one"}[1] )` – Samuel Kirschner Sep 05 '18 at 13:58
  • The comment about lambdas being more efficient is suspect as well. It may well be more expensive to construct two `function` objects and subsequently call one of them than it would be to simply evaluate both `a` and `b` in the first place (which happens anyway to compute `a < b`, so the entire construct is somewhat inefficient). – chepner Sep 05 '18 at 13:58

3 Answers3

1

a<b is a bool (True or False). Since bool is a subtype of int, it can be used in a context where an integer is expected, e.g. as a list/tuple index:

>>> True == 1
True
>>> False == 0
True
>>> isinstance(True, int)
True
>>> ('a', 'b')[True]  # True == 1
'b'
>>> ('a', 'b')[1<2]  # 1<2 == True == 1
'b'
>>> ('a', 'b')[2<1]  # 2<1 == False == 0
'a'

For dict keys, it is similar, but the type coercion is not even needed:

>>> {True: 'a', False: 'b'}[1<2]  # 1<2 == True
'a'
user2390182
  • 72,016
  • 6
  • 67
  • 89
1

First note that all these give the minimum value of a and b:

a, b = 10, 20

res1 = (b, a)[a < b]                    # 10
res2 = {True: a, False: b}[a < b]       # 10
res3 = (lambda: b, lambda: a)[a < b]()  # 10

We can consider these in turn:

  1. res1 constructs a tuple of 2 integers. a < b returns a Boolean value, in this case True. In Python, True == 1, as bool is a subclass of int. Finally, [] is syntactic sugar for __getitem__, which is the method called for positional indexing. Since Python starts counting from 0, the first index is a. You can also confirm this yourself: (b, a).__getitem__(1) returns 10.
  2. res2 constructs a dictionary mapping Boolean values to a & b. Calling __getitem__ on a dict object returns the value for a given key. Here the key, as before, is True. Since the dictionary maps True to a, this is the value returned.
  3. res3 constructs a tuple of anonymous (lambda) functions, each returning scalars, namely b and a. As per res1, an item can be extracted from a tuple via integer indexing. The only additional requirement is to actually call the lambda functions via ().

Note none of these operate the same way as the ternary operator which is applied at compile time (see comments) via an in-line if / else:

res4 = a if a < b else b
jpp
  • 159,742
  • 34
  • 281
  • 339
  • What do you mean "the ternary operator is applied at compile time"? It is a real conditional evaluated at runtime (unlike a C preprocessor #ifdef, for example). – alexis Sep 05 '18 at 14:10
  • @alexis, Python files are technically [compiled to bytecode](https://stackoverflow.com/a/6889798/9209546). The bytecode is then interpreted in CPython. You are right, if I understand what you mean, that there's no compilation to machine code. – jpp Sep 05 '18 at 14:11
  • Nah, that would be splitting hairs. I mean something more relevant: A compile-time conditional chooses a branch based on what's true when the program is read/preprocessed/compiled. E.g., `#if OS == "darwin"` Then the same branch will always be followed-- in effect, the conditional is not there anymore. But a Python ternary is a real if, and will be evaluated anew (sometimes true, sometimes false) every time the program flow hits it. – alexis Sep 06 '18 at 07:02
1

Your misconception might be regarding a < b .

In all these cases the boolean result of evaluating a < b is used as a key for the object in before.

In the case of

print( (b, a) [a < b] )

and

print((lambda: b, lambda: a)[a < b]())

the object is a tuple containing either the variables themselves or very simple anonymus functions, that return these variables.

In the case of

print({True: a, False: b} [a < b])

the expression is evaluated and used as a key for the dictionary, which has both True and False as keys. The assumption that this means, that it must be an older Python version is incorrect thoug, because a dictionary does not represent a reassignment of values, but is merely a data structure, where a key maps to a value. True and False are valid keys and this circumstance is precisely, what is being used here.

Finally:

 print( a if a < b else b )

Is a nice and concise may of expressing the same thing and in fact the line of code I would use in this case

MTTI
  • 391
  • 2
  • 10