this or that: The or operator in Python

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

This presentation is available at 20181027-ccc-or-operator.ipynb.


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
!cd ~/20181027/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 [4]:
False or 0


Out[4]:
0

In [3]:
0 or 0


Out[3]:
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]:
'this' or 'that'


Out[14]:
'this'

In [15]:
'' or 'that'


Out[15]:
'that'

In [16]:
'this' or ''


Out[16]:
'this'

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 [ ]:
'' or (1, 3)

In [25]:
'' or 'False'


Out[25]:
'False'

In [26]:
'' or 'True'


Out[26]:
'True'

In [27]:
'' or True


Out[27]:
True

In [28]:
'' or False


Out[28]:
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 [29]:
True


Out[29]:
True

In [34]:
int(True), int(False), True + True, True - True, 0 + True, True + 0


Out[34]:
(1, 0, 2, 0, 1, 1)

In [35]:
True / False


---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-35-f8487d9d0863> in <module>()
----> 1 True / False

ZeroDivisionError: division by zero

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


Out[38]:
0.5

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


Out[39]:
0

In [40]:
None + 0, 0 + None


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-40-84e5ed69a096> in <module>()
----> 1 None + 0, 0 + None

TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

In [41]:
bool(None)


Out[41]:
False

In [36]:
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 [37]:
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 [51]:
True + True


Out[51]:
2

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


Out[52]:
0.5

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


Out[53]:
0

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


In [42]:
'' or 1


Out[42]:
1

In [43]:
'' or 2


Out[43]:
2

In [44]:
'fizz' or 3


Out[44]:
'fizz'

In [45]:
'buzz' or 5


Out[45]:
'buzz'

In [46]:
'fizz' or 6


Out[46]:
'fizz'

In [47]:
'fizzbuzz' or 15


Out[47]:
'fizzbuzz'

In [48]:
'' or 16


Out[48]:
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 [49]:
False or 0 or 0j or 0.0 or [] or {} or set() or None or ()


Out[49]:
()

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


Out[50]:
'false'

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 [ ]:
False and 1

In [ ]:
'False' and 1

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

Joe Shaw: The 'wat' lightning talk by Gary Bernhardt.


2018-10-28 afterthoughts


In [64]:
bool(True)


Out[64]:
True

In [65]:
bool(-True)


Out[65]:
True

In [66]:
bool(True + 1j)


Out[66]:
True

In [67]:
bool(True - 1j)


Out[67]:
True

In [68]:
bool(True + 1j**2)


Out[68]:
False