2. Basic Types: Booleans
Precedence and Associativity
Now that we have seen simple boolean logic using the #\mathtt{\text{or}}#, #\mathtt{\text{and}}# and #\mathtt{\text{not}}# operators, we can take a look at some more complex boolean expressions. Naturally, we can chain booleans and boolean operators to account for different scenarios.
Simply chaining operators and booleans:
>>> 1 and 1 and 1
|
1
|
>>> 1 and 1 and 0
|
0
|
However, chaining would be very limited if there wasn't a way to modify the evaluation order of sub-expressions. The order of evaluation is determined by operator precedence and associativity. Luckily there is a way to bend the order to our will: parentheses. We can use parentheses to assign priority to certain sub-expressions, just like we would in mathematics.
PrecedenceOperator precedence or order of operations is a collection of rules that reflect conventions about which procedures to evaluate first in a given expression.
In math and in Python, multiplication has precedence over summation, i.e. the #\mathtt{\text{*}}# operator has higher precedence than the #\mathtt{\text{+}}# operator; sub-expressions containing #\mathtt{\text{*}}# are evaluated first. The exponent #\mathtt{\text{**}}#, or #\mathtt{\text{^}}# operator on calculators, has higher precedence than both multiplication and summation.
In Python boolean logic, the precedence of the three discussed operators goes: #\mathtt{\text{not}}# > #\mathtt{\text{and}}# > #\mathtt{\text{or}}#, so negation has the highest precedence of these operators, then conjunction, then disjuction.
>>> 1 or 1 and 0
|
1
|
The subexpression #\mathtt{\color{#F5871F} {\text{1}} \color{#8959A8}{\text{ and }} \color{#F5871F} {\text{0}}}# is evaluated to #\color{#F5871F}{\mathtt{\text{0}}}# first, then its value is substituted in the rest of the expression, which now becomes #\mathtt{\color{#F5871F}{\text{1}} \color{#8959A8}{\text{ or }} \color{#F5871F}{\text{0}}}#, which evaluates to #\color{#F5871F}{\mathtt{\text{1}}}#.
>>> not 1 or 1
|
1
|
The subexpression #\mathtt{ \color{#8959A8 }{\text{not }} \color{#F5871F}{\text{1}}}# is evaluated first to #\mathtt{\color{#F5871F}{\text{0}}}#, then its value is substituted in the rest of the expression, which now becomes #\mathtt{\color{#F5871F}{\text{0}} \color{#8959A8}{\text{ or }} \color{#F5871F}{\text{1}}}#, which evaluates to #\color{#F5871F}{\mathtt{\text{1}}}#. To show why precedence matters we can use parentheses in these expressions to manually alter the order of operations, which will alter their respecive results. Parentheses have the highest precedence of all Python operators.
>>> (1 or 1) and 0
|
0
|
Now, the subexpression #\mathtt{\color{#F5871F}{\text{1}} \color{#8959A8}{\text{ or }} \color{#F5871F}{\text{1}}}# is evaluated first, which alters the result of the entire expression to #\color{#F5871F}{\mathtt{\text{0}}}#.
>>> not (1 or 1)
|
False
|
Here, the subexpression #\mathtt{\color{#F5871F}{\text{1}} \color{#8959A8}{\text{ or }} \color{#F5871F}{\text{1}}}# is now evaluated first as well, which alters the result of the entire expression to #\color{#F5871F}{\mathtt{\text{False}}}#.
But what happens to the order of operations when we have an expression with two of the same operators? That is where associativity comes into play.
AssociativityThe associativity of an operator is a property that determines in what order operators of the same precedence are grouped and evaluated in the absence of parentheses.
We recognise four distinct types of associativity: associative, left-associative, right-associative and non-associative. Associative operators can be grouped arbitrarily, left- and right-associative operators are grouped from the left or right respectively and non-associative operators cannot be chained at all. Both associativity and precedence are part of the definition of a programming language, as a result they can differ between programming languages.
In Python, almost all operators are left-associative, this includes #\mathtt{\text{or}}#, #\mathtt{\text{and}}#, and #\mathtt{\text{not}}#. This means that the chaining example shown above is actually evaluated as #\mathtt{\text{(}}##\mathtt{\text{1}}# #\mathtt{\text{and}}# #\mathtt{\text{1}}##\mathtt{\text{)}}# #\mathtt{\text{and}}# #\mathtt{\text{1}}#. Since none of the boolean operators share the same precedence, associativity is rarely an issue. Mathematical operators are once again a better example; #\mathtt{\text{+}}# and #\mathtt{\text{-}}# are two different operators that share the same precedence.
Because #\mathtt{\text{+}}# and #\mathtt{\text{-}}# are left-associative the expression is grouped from the left and thus evaluated as #\mathtt{\text{(}\color{#F5871F} {\text{7}}\color{#3e999f}{\text{ - }}\color{#F5871F}{\text{4}}\text{)}\color{#3e999f}{\text{ + }}\color{#F5871F}{\text{2}}}#.
>>> 7 - 4 + 2
|
5
|
If #\mathtt{\text{+}}# and #\mathtt{\text{-}}# were right-associative, the expression would be grouped from the right, which would result in a different result. If the associativity wasn't defined by the programming language, we wouldn't know the correct result for an expression like this without parentheses.
>>> 7 - (4 + 2)
|
1
|
In the upcoming theory pages we will see that not all Python operators are left-associative.
Or visit omptest.org if jou are taking an OMPT exam.