0

I am learning python list comprehension and saw a usage and I couldn't find explanation in official document.

>>>[(not i%2)*'a' or str(i) for i in range(10)]
>>>['a', '1', 'a', '3', 'a', '5', 'a', '7', 'a', '9']

I am trying to abstract this usage in this way:

[statement1 or statement2 for i in range(10)]

and if statement1 is evaluated as false, then use statement 2, am I right?

I also discover that if I run the following command:

>>> [(not i%2)*'a' and str(i) for i in range(10)]

The output would be:

>>> ['0', '', '2', '', '4', '', '6', '', '8', '']

How should I understand these?

smci
  • 26,085
  • 16
  • 96
  • 138
Captain Rib
  • 255
  • 5
  • 16
  • This is almost surely a duplicate of existing Q&A. – smci Oct 27 '16 at 23:29
  • And `(not i%2)` is an idiom to check if an integer is odd; there are at least 6 existing questions on that. To be more clear, we would declare a predicate function `is_odd(n)`. – smci Oct 27 '16 at 23:37
  • `true_value and cond or false_value` is the old [Python pre-2.5 idiom for implementing a ternary operator (?: or ifelse)](https://stackoverflow.com/questions/394809/does-python-have-a-ternary-conditional-operator). The [newer idiom is `true_value if cond else false_value`](http://stackoverflow.com/a/394814/202229). It's irrelevant whether this is in a list-comprehension or elsewhere. Anyway, this has been asked many times before. Also: ["Python conditional operator 'if else' not equal 'and or'"](https://stackoverflow.com/questions/33780600/python-conditional-operator-if-else-not-equal-and-or) – smci Oct 27 '16 at 23:51

4 Answers4

2

That's right. The weird part is the first expression:

(not i%2)*'a'

If i is odd, then this evaluates to 'a'; if even, it's False. This is a blatant abuse (a.k.a. expert trick) of boolean definition in Python.

If you imagine True as 1 and False as 0, it makes sense. If the product is 0, the or operator takes it as False.

This also takes advantage of expression short-circuiting: if the left element of the or is True, then the right one isn't evaluated at all, and that left element becomes the expression's value.

Similarly, in your later example, the and short-circuit works the other way: if the left element is False, then it becomes the result; otherwise, the right element becomes the result.

Prune
  • 72,213
  • 14
  • 48
  • 72
  • "otherwise, the right element becomes the result." Why in this case we only take the right element as the result? – Captain Rib Oct 27 '16 at 23:31
  • Because George Boole proved as a lemma that **True and x** must be **x** for all **x**. Similarly, **False or x** must be **x** for all **x**. – Prune Oct 27 '16 at 23:34
0

Here or return the first value that can be evaluated as True . So if the first statement return value can be evaluated as True, just use it or search for the next value. Try to add more 'or'

 [(not i%3)*'a' or (not i%5)*'b'  or 'c' for i in range(10)]
 => ['a', 'c', 'c', 'a', 'c', 'b', 'a', 'c', 'c', 'a']

And returns the last value only if all the values can be evaluated to True

Shady Atef
  • 1,831
  • 1
  • 15
  • 34
0

The statement explained:

not i%2: i % 2 evaluates to 0 for even integers, 1 for odd integers. not will result in a boolean that negates the following expression. not 1 => False, and not 0 => True.

(True)*'a' => 1 * 'a' => 'a'

(False)*'a' => 0 * 'a' => ''

'', when cast to boolean, is evaluated as False

False or str(i) => str(i)

Jordan McQueen
  • 686
  • 4
  • 8
0

If you turn that into a list of tuples (with both conditions included), it is easier to see what is happening:

>>> [((not i%2)*'a', str(i)) for i in range(10)]
[('a', '0'), ('', '1'), ('a', '2'), ('', '3'), ('a', '4'), ('', '5'), ('a', '6'), ('', '7'), ('a', '8'), ('', '9')]

Any odd value of i produces '' which is False in a Python sense. The or then evaluates the right hand side and returns that rather than a left hand side:

>>> 'LH' or 'RH'
'LH'
>>> '' or 'RH'
'RH'

It is better stated as a conditional expression because it is easier to read:

>>> ['a' if i%2==0 else str(i) for i in range(10)]
['a', '1', 'a', '3', 'a', '5', 'a', '7', 'a', '9']
dawg
  • 80,841
  • 17
  • 117
  • 187