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.
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.
...
Here are most of the built-in objects considered false:
...
Objects that are not false are true.
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]:
In [2]:
0 or False
Out[2]:
In [3]:
False or 0
Out[3]:
In [4]:
0 or 0
Out[4]:
In [5]:
False or True
Out[5]:
In [6]:
True or False
Out[6]:
In [7]:
True or True
Out[7]:
In [8]:
True or 1
Out[8]:
In [9]:
1 or True
Out[9]:
In [10]:
1 or 1
Out[10]:
In [11]:
1 > 2
Out[11]:
In [12]:
3 < 4
Out[12]:
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]:
In [14]:
'hello' or 'world'
Out[14]:
In [15]:
'' or 'world'
Out[15]:
In [16]:
'hello' or ''
Out[16]:
In [17]:
'' or ''
Out[17]:
In [18]:
'' or None
In [19]:
False or 3.14
Out[19]:
In [20]:
'False' or 3.14
Out[20]:
In [21]:
bool('False' or 3.14)
Out[21]:
In [22]:
[] or {}
Out[22]:
In [23]:
'' or []
Out[23]:
In [24]:
'' or {}
Out[24]:
In [25]:
'' or (1, 3)
Out[25]:
In [26]:
'' or 'False'
Out[26]:
In [27]:
'' or 'True'
Out[27]:
In [28]:
'' or True
Out[28]:
In [29]:
'' or False
Out[29]:
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()
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]:
In [33]:
True / (True + True)
Out[33]:
In [34]:
True // (True + True)
Out[34]:
Now we get to how the "or" operator was used in fizzbuzz().
In [35]:
'' or 1
Out[35]:
In [36]:
'' or 2
Out[36]:
In [37]:
'fizz' or 3
Out[37]:
In [38]:
'buzz' or 5
Out[38]:
In [39]:
'fizz' or 6
Out[39]:
In [40]:
'fizzbuzz' or 15
Out[40]:
In [41]:
'' or 16
Out[41]:
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]:
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]:
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]:
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]:
In [50]:
foo()
Out[50]:
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]:
In [53]:
foo()
Out[53]:
In [54]:
a = foo()
a[1] = 'hi mom'
a
Out[54]:
The cell above changes the mutable default argument, as shown below.
In [55]:
foo()
Out[55]:
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]:
In [58]:
b[2] = 'this'
b
Out[58]:
In [59]:
foo()
Out[59]:
How can I screw up Zach's version? It is sensitive to false arguments.
In [60]:
foo([1])
Out[60]:
In [61]:
foo([])
Out[61]:
In [62]:
foo(0)
Out[62]:
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]:
In [65]:
foo(None)
Out[65]:
In [66]:
foo([1])
Out[66]:
In [67]:
foo([])
Out[67]:
In [68]:
foo(0)
Out[68]:
Maybe a better name for this presentation would be 'this' or 'that'.
In [69]:
'this' or 'that'
Out[69]:
In [70]:
'give me liberty' or 'give me death'
Out[70]:
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]:
In [72]:
'False' and 1
Out[72]:
By the way, Python objects can define their own truthiness by defining the __bool__() or __len__() methods.