"or" operator

This presentation is inspired by Neil Ludban's use of the "or" operator in Test-Driven Development with Python at last month's technical meeting.


In [ ]:
# meld is a great visual difference program
# http://meldmerge.org/

# the following command relies on the directory structure on my computer
# tdd-demo comes from https://github.com/james-prior/tdd-demo/

!cd ~/projects/tdd-demo;git difftool -t meld -y 389df2a^ 389df2a

Python's or operator has some similarities with C's || operator.

  • Both always evaluate the first operand.
  • Both guarantee that the second operand is not evaluated if the first operand is true.

C's || operator yields an integer: 0 (false) or 1 (true).

Python "or" operator yields one of the operands. If the first operand is true, it is the result and the second operand is guaranteed to not be evaluated. If the first operand if false, the second operand is evaluated and is the result.

Note that Python returns one of the operands, not merely 0 or 1.

Python's concept of truth

...

Here are most of the built-in objects considered false:

  • constants defined to be false: None and False.
  • zero of any numeric type: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
  • empty sequences and collections: '', (), [], {}, set(), range(0)

...

Objects that are not false are true.

  • constants defined to be true: True.
  • not zero of any numeric type: 1, -1, 0.01, 1j, Decimal(1), Fraction(-1, 1)
  • non-empty sequences and collections: 'hello', (0,), [0], {0: 0}, set([0]), range(1)

For each of the following cells, predict the output.

During the meeting, there was much disagreement in predictions, especially in the first four cells below. That led to real learning.


In [1]:
False or False


Out[1]:
False

In [2]:
0 or False


Out[2]:
False

In [3]:
False or 0


Out[3]:
0

In [4]:
0 or 0


Out[4]:
0

In [5]:
False or True


Out[5]:
True

In [6]:
True or False


Out[6]:
True

In [7]:
True or True


Out[7]:
True

In [8]:
True or 1


Out[8]:
True

In [9]:
1 or True


Out[9]:
1

In [10]:
1 or 1


Out[10]:
1

In [11]:
1 > 2


Out[11]:
False

In [12]:
3 < 4


Out[12]:
True

In [13]:
# This kind of expression using the "or" operator is very typical,
# comprising the vast majority of use.

1 > 2 or 3 < 4


Out[13]:
True

In [14]:
'hello' or 'world'


Out[14]:
'hello'

In [15]:
'' or 'world'


Out[15]:
'world'

In [16]:
'hello' or ''


Out[16]:
'hello'

In [17]:
'' or ''


Out[17]:
''

In [18]:
'' or None

In [19]:
False or 3.14


Out[19]:
3.14

In [20]:
'False' or 3.14


Out[20]:
'False'

In [21]:
bool('False' or 3.14)


Out[21]:
True

In [22]:
[] or {}


Out[22]:
{}

In [23]:
'' or []


Out[23]:
[]

In [24]:
'' or {}


Out[24]:
{}

In [25]:
'' or (1, 3)


Out[25]:
(1, 3)

In [26]:
'' or 'False'


Out[26]:
'False'

In [27]:
'' or 'True'


Out[27]:
'True'

In [28]:
'' or True


Out[28]:
True

In [29]:
'' or False


Out[29]:
False

Python's concept of truthiness

Numerical values are false if zero and true if not zero. None is false. Sequences and collections are false if empty, and true if not empty.


In [30]:
values = (
    None,
    0,
    0.0,
    0j,
    (),
    [],
    {},
    set(),
    False,
    True,
    True + True,
    (True + True + True) / True,
    1,
    -1,
    1.e-30,

    '',
    'False',
    'True',
    
    [],
    [None], # This fools many people.
    [0],
    [0.0],
    [0j],
    [1],
    [1, 2],
    [[]], # This fools many people.
    [{}],
    [()],
    [],
    
    (),
    (None,),
    (0,),
    (0.0,),
    (0j,),
    (1,),
    (1, 2),
    ([],),
    ({},),
    ((),),
    (),
    
    {},
    {None: None},
    {False: None},
    {'False': None},
    
    set(),
    {None},
    {0},
    {0.0},
    {0j},
    {1},
    {1, 2},
    {()},
)

Slowly scroll through the output of the following cell, predicting the output of each value before scrolling to reveal the actual output.


In [31]:
for value in values:
    print(repr(value), type(value))
    print(bool(value))
    print()


None <class 'NoneType'>
False

0 <class 'int'>
False

0.0 <class 'float'>
False

0j <class 'complex'>
False

() <class 'tuple'>
False

[] <class 'list'>
False

{} <class 'dict'>
False

set() <class 'set'>
False

False <class 'bool'>
False

True <class 'bool'>
True

2 <class 'int'>
True

3.0 <class 'float'>
True

1 <class 'int'>
True

-1 <class 'int'>
True

1e-30 <class 'float'>
True

'' <class 'str'>
False

'False' <class 'str'>
True

'True' <class 'str'>
True

[] <class 'list'>
False

[None] <class 'list'>
True

[0] <class 'list'>
True

[0.0] <class 'list'>
True

[0j] <class 'list'>
True

[1] <class 'list'>
True

[1, 2] <class 'list'>
True

[[]] <class 'list'>
True

[{}] <class 'list'>
True

[()] <class 'list'>
True

[] <class 'list'>
False

() <class 'tuple'>
False

(None,) <class 'tuple'>
True

(0,) <class 'tuple'>
True

(0.0,) <class 'tuple'>
True

(0j,) <class 'tuple'>
True

(1,) <class 'tuple'>
True

(1, 2) <class 'tuple'>
True

([],) <class 'tuple'>
True

({},) <class 'tuple'>
True

((),) <class 'tuple'>
True

() <class 'tuple'>
False

{} <class 'dict'>
False

{None: None} <class 'dict'>
True

{False: None} <class 'dict'>
True

{'False': None} <class 'dict'>
True

set() <class 'set'>
False

{None} <class 'set'>
True

{0} <class 'set'>
True

{0.0} <class 'set'>
True

{0j} <class 'set'>
True

{1} <class 'set'>
True

{1, 2} <class 'set'>
True

{()} <class 'set'>
True

There was some confusion and disbelief that True and False are integers, so that was played with.

Some folks also did not know about the distinction between the / and // operators in Python 3, so that was played with also.


In [32]:
True + True


Out[32]:
2

In [33]:
True / (True + True)


Out[33]:
0.5

In [34]:
True // (True + True)


Out[34]:
0

Now we get to how the "or" operator was used in fizzbuzz().


In [35]:
'' or 1


Out[35]:
1

In [36]:
'' or 2


Out[36]:
2

In [37]:
'fizz' or 3


Out[37]:
'fizz'

In [38]:
'buzz' or 5


Out[38]:
'buzz'

In [39]:
'fizz' or 6


Out[39]:
'fizz'

In [40]:
'fizzbuzz' or 15


Out[40]:
'fizzbuzz'

In [41]:
'' or 16


Out[41]:
16

Now we get to a more serious discussion of when it is good to use the "or" operator where the operands are not merely the typical case of False or True.

The short answer is:

Use the "or" operator when it makes the code more readable.

But that begs the question.

When does using the "or" operator make the code more readable?

This is a question I have been struggling with.

Let's go back to the old code at hand.

if not output:
    return str(n)
return output

It returns either output or str(n). When said that simply in English, Neil's refactoring is the most readable code. It says most simply and directly what we want.

return output or str(n)

The problem may be from the biases of experienced programmers like myself who expect the "or" operator to yield only a true or false value, like we expect from other languages such as but not limited to C. Inexperienced folks do not bring such baggage from other languages.

For myself, I have decided to absorb and use the idiom like Neil showed us. It is part of learning Python.


With a long string of "or"ed stuff, the result is the first true operand. If no operands are true, the result is the last operand.


In [42]:
False or 0 or 0j or 0.0 or [] or {} or set() or None or ()


Out[42]:
()

In [43]:
False or 0 or 0j or 0.0 or 'false' or [] or {} or set() or None or ()


Out[43]:
'false'

Pete Carswell asked about doing the above long expressions with a lamdba, hence the following.


In [44]:
from functools import reduce

In [45]:
a = (
    False,
    0,
    0j,
    0.0,
    [],
    {},
    'look ma no hands',
    set(),
    None,
    (),
)

In [46]:
reduce(lambda x, y: x or y, a)


Out[46]:
'look ma no hands'

Note that the reduce() evaluates all the elements of its second operand, whereas a big long multiple "or" expression is guaranteed to stop evaluating operands after the first true operand.

Then I thought the operator module should eliminate the need for the lamdba, so I explored the operator module.


In [47]:
import operator

[s for s in dir(operator) if 'or' in s]


Out[47]:
['__floordiv__',
 '__ifloordiv__',
 '__ior__',
 '__ixor__',
 '__or__',
 '__xor__',
 'floordiv',
 'ifloordiv',
 'ior',
 'ixor',
 'or_',
 'xor']

Unfortunately, I was not able to find an equivalent to the "or" operator.


Zach brought up another use of the "or" operator for handling default arguments.


In [48]:
def foo(p=None):
    p = p or [1, 2, 3, 4]
    return p

It is too bad that there is not an or= operator. C does not have a ||= operator either.


In [49]:
foo(5)


Out[49]:
5

In [50]:
foo()


Out[50]:
[1, 2, 3, 4]

Zach prefers his code above to code below, with the danger of its mutable default value.


In [51]:
def foo(p=[1, 2, 3, 4]):
    return p

In [52]:
foo(3)


Out[52]:
3

In [53]:
foo()


Out[53]:
[1, 2, 3, 4]

In [54]:
a = foo()
a[1] = 'hi mom'
a


Out[54]:
[1, 'hi mom', 3, 4]

The cell above changes the mutable default argument, as shown below.


In [55]:
foo()


Out[55]:
[1, 'hi mom', 3, 4]

Zach's version does not suffer from the mutable default argument problem.


In [56]:
def foo(p=None):
    p = p or [1, 2, 3, 4]
    return p

In [57]:
b = foo()
b


Out[57]:
[1, 2, 3, 4]

In [58]:
b[2] = 'this'
b


Out[58]:
[1, 2, 'this', 4]

In [59]:
foo()


Out[59]:
[1, 2, 3, 4]

How can I screw up Zach's version? It is sensitive to false arguments.


In [60]:
foo([1])


Out[60]:
[1]

In [61]:
foo([])


Out[61]:
[1, 2, 3, 4]

In [62]:
foo(0)


Out[62]:
[1, 2, 3, 4]

That can be fixed with a traditional "is None" test.


In [63]:
def foo(p=None):
    if p is None:
        p = [1, 2, 3, 4]
    return p

In [64]:
foo()


Out[64]:
[1, 2, 3, 4]

In [65]:
foo(None)


Out[65]:
[1, 2, 3, 4]

In [66]:
foo([1])


Out[66]:
[1]

In [67]:
foo([])


Out[67]:
[]

In [68]:
foo(0)


Out[68]:
0

Maybe a better name for this presentation would be 'this' or 'that'.


In [69]:
'this' or 'that'


Out[69]:
'this'

In [70]:
'give me liberty' or 'give me death'


Out[70]:
'give me liberty'

Zach reported that the "or" operator in Javascript works like Python.


This presentation concentrated on the "or" operator. The "and" operator works like you (should) expect. Explore it on your own.


In [71]:
False and 1


Out[71]:
False

In [72]:
'False' and 1


Out[72]:
1

By the way, Python objects can define their own truthiness by defining the __bool__() or __len__() methods.