(PEP-308) Python's Conditional Selection Operators

Terry Reedy tjreedy at udel.edu
Fri Feb 14 19:24:50 EST 2003


PYTHON'S CONDITIONAL SELECTION OPERATORS: 'AND' AND 'OR'

Some slightly different thougths (as of 2003 Feb 14) ...

Conflicting meanings
--------------------
Python's 'and' and 'or' are not the standard versions:

In logic, math, and computing science, 'and' and 'or' have well-known
standard meanings as logical operators, either strict or
domain-extended via operand coercion, and either conditional in
execution or not.

In Python, 'and' and 'or' are generalized as conditional selection
operators (as explained in following sections), with some unusual
properties.

The difference is slightly tricky.  When both operands are logical
values, the selection result is the same as in strict logic.  On the
extended domain, the result is usually different from that of the
corresponding extended logical operator.  But coercing the result to
boolean washes away the difference..


Logic and conditional execution:
--------------------------------
Binary logic operators map two logic values to a logic value.  The
domain can be extended by implicitly coercing non-logic values to
logic values.  The result is still a logic value: a extended-op b ==
bool(a) strict-op bool(b).

Like multiplication, where 0*a=0, logical 'and' and 'or' have dominant
values: 0 and a = 0, 1 or a = 1, where 0,1 are False, True.  For
efficiency, execution of the second operand can be conditional on the
first not being dominant.  The possible problem is when execution of b
has a side-effect that also becomes conditional.

Conditional statements (if, elif, else) extend conditional execution
from expressions to blocks of code.  Python's conditional statements
are lax (like extended operaters) in that they accept any expression
as a condition and then implicitly coerce the result to a boolean
value:
if bool(expression): # has the same effect as
if expression:

In a sense, conditional statements makes conditional (extended) logic
operators redundant:

tem = a and b # is equivalent to
tem = bool(a)
if tem: tem = bool(b)

tem = a or b # is equivalent to
tem = bool(a)
if not tem: tem = bool(b)

However, they are still very convenient to have.


Selection and Logic
-------------------
A selection operation selects one (or more) items from many.  The
getitem/getattribute operator [a] selects an item from a collection
object.  Slices select contiguous chunks from sequences.  Filter()
selects 0 to all items produced by an iterable.

In Python, 'and' and 'or' conditionally select one of their two
operands:

tem = a and b # is equivalent to
tem = a
if tem: tem = b

tem = a or b # is equivalent to
tem = a
if not tem: tem = b

These statement equivalents are identical, except for omission of
bool() coercions, to the statements equivalent to conditional extended
logic ops.

When both a and b are boolean, so is the output, making selection and
logic the same.  They are also the same when the selection result is
coerced to boolean.  Since if and elif implicitly do such coercion,
selection acts like logic when it is the outer operation of an if/elif
condition.  But in other contexts, it does not.  Thus, the effect of
'if (a and b):' does not depend on which meaning 'and' has, while that
of 'if (a and 2)==2:' does.

A non-trival binary logic operator is one whose output depends on both
inputs.  Of the ten possible, only and and or are selective in that
the output is always one of the inputs. (To be selective, 0 op 0 and 1
op 1 must be 0 and 1 respectively.  Of the four possible assignments
to 0 op 1 and 1 op 0, two produce the trivial selectors a op b == a
and a op b == b.  The other two are and and or.)  They are thus the
only two that can be generalized in the way they are in Python.


Selective 'and' and 'or'
------------------------
The 'conditional' aspect of Python's 'and' and 'or' can be used to
guard execution of a possibly impossible or illegal expression.  When
a null default is acceptible, 'possible and expression' works.  If a
truthful default is wanted, then use 'impossible or expression'
instead.

Selective 'and' and 'or' have an unusual combination of properties.
Every Python value is a left identity (id op b == b) for one of the
two.  They are non-commutative (in general, a op b != b op a) but
become commutative when coerced to boolean.

Both are associative ((a and b) and c) == a and (b and c); (a or b) or
c == a or (b or c)) as can be verified by truth-outcome tables.
Iterated 'a and b and ... and z' selects the first null value or the
default z. Iterated 'a or b or ... or z' selects the first non-null
value or, again, the last.

The mixed combinations 'a and (b or c)' and 'a or (b and c)' select
one of the three inputs, depending of the truth values of a and b.
The first selects the first of null a, non-null b, or any c.  The
second switches null and non-null. Expressions parenthesized to the
right sequentially examine values and select the first whose boolean
value dominates the following operator.

The reversed expressions '(a and b) or c' and '(not a or b) and c' are
the most relevant to PEP-308 and the quest for an 'if a then b else c'
ternary.  When a is False, both select c.  When a is True, the first
selects True b while the second selects False b.  So if we know the
truth value of one of the selection targets, there is an and/or
combination that gives the right answer.  For instance, if x then
x.val else 0 == (x or 0) and x.val.

For the general case, the two expressions combine to exactly match 'if
a then b else c':
  (a and b) or ((not a or b) and c) ==
  (not a or b) and ((a and b) or c).
However, these are sufficiently baroque that most any of the current
or proposed alternatives should be better.


Terry J. Reedy






More information about the Python-list mailing list