Python Part I · Module 3 · Operators and Expressions
Precedence, Parentheses and Saying What You Mean
Three chapters in this module have deferred the same question. When an expression contains more than one operator, which one does Python apply first? This chapter answers it, and then argues that knowing the answer is not usually the point. Python does have a defined order, and a handful of those rules are worth carrying in your head because they come up constantly. Beyond that handful, the professional response to an expression whose order is not obvious is not to prove you can work it out — it is to add parentheses so that nobody else has to. This is the last chapter of Module 3, and it closes with the habit that makes everything you have learned readable to the next engineer.
Foundation10 min readPythonPart IFundamentalsPrecedenceReadability
Part I · Module 3 · Chapter 3.8 · Precedence, Parentheses and Saying What You Mean
1. The Problem
print(2 + 3 * 4)14Not 20. If Python worked strictly left to right it would add first — 2 + 3 is 5, times 4 is 20. It did not.
Python applied the multiplication first, then the addition:
2 + 3 * 4
↓ multiplication binds more tightly
2 + 12
↓
14This is precedence: some operators bind more tightly than others, and the tighter ones are applied first. It is the same rule you learned for arithmetic at school, and Python follows the conventional one.
2. Parentheses Win
Parentheses group part of an expression so it is worked out as a unit:
print(2 + 3 * 4)
print((2 + 3) * 4)14
20Chapter 3.1 §5 used this to show the order explicitly, precisely so that precedence did not have to be assumed. Now it can be named: whatever is inside parentheses is worked out first, whatever the operators involved.
3. The Rules Worth Knowing
There is a full precedence order in Python, and most of it is not worth memorising. Four groupings cover nearly everything you will meet.
Arithmetic before comparison. The arithmetic is done, then the comparison asks about the result.
total = 600
passed = 589
print(total - passed == 11)Truetotal - passed produced 11, and then == 11 compared it. That is Chapter 3.4 §7, now explained.
Comparison before and / or. Each comparison produces its bool, then the logical operator combines them.
error_count = 0
coverage = 96.2
print(error_count == 0 and coverage >= 95.0)TrueBoth comparisons were evaluated first. This is why Chapter 3.5 §3 needed no brackets.
not before and, and and before or.
print(True or False and False)TrueThe and was applied first, giving False, and then True or False gave True. Had the or gone first, the answer would have been False.
Within arithmetic: ** binds tightest, then * / // %, then + -.
print(2 + 3 ** 2)
print(2 * 3 ** 2)11
18The power was applied before the addition and before the multiplication in each case.
Here they are compressed. This table is for reference, not memorisation — the four statements above are what you actually carry.
| Binds tightest | Operators |
|---|---|
| 1 | () — always wins |
| 2 | ** |
| 3 | ~, unary - |
| 4 | * / // % |
| 5 | + - |
| 6 | << >> |
| 7 | & |
| 8 | ^ |
| 9 | | |
| 10 | == != < <= > >= in not in is is not |
| 11 | not |
| 12 | and |
| 13 (loosest) | or |
4. The One Where Python Differs From C
Look at rows 7 and 10. In Python the bitwise operators bind more tightly than the comparisons.
That is the sensible order, and it means the obvious way to test a masked bit works as written:
flags = 0b1010
print(flags & 0b0010 == 0b0010)TruePython grouped it as (flags & 0b0010) == 0b0010 — mask first, then compare — which is what the line looks like it means.
C orders these two the other way round. In C, == binds tighter than &, so the identical-looking line groups as flags & (0b0010 == 0b0010), which is a completely different question. You can make Python do that too, by asking for it:
flags = 0b1010
print(flags & (0b0010 == 0b0010))0The comparison produced True, and flags & True is a bitwise operation against 1, giving 0.
5. Parentheses Are for Readers
Here is the argument this chapter actually wants to make.
Parentheses are usually introduced as a way to override precedence. That is one job. The more valuable one is that they let a reader stop thinking.
error_count = 0
finished = True
timed_out = False
print(error_count == 0 and finished and not timed_out)
print((error_count == 0) and (finished and not timed_out))True
TrueIdentical results. The second version changes nothing Python does — and it tells a reader, instantly, that the question has two halves, without their having to recall where not sits relative to and.
That is worth the four characters.
A few cases where brackets earn their place even though the expression is already correct:
status = 0b10110110
print(status & (1 << 4))
print((status & 0b1111) == 0b0110)
print((600 - 589) / 600)16
True
0.018333333333333333The first two were discussed above. The third does not need brackets at all — - binds tighter than / — but bracketing the subtraction says this is a difference, and then a rate, which is what the line means.
6. What Not to Do
Two failure modes, in opposite directions.
Do not write expressions that depend on obscure precedence. If a line is correct only because of a rule you had to look up, the next reader will have to look it up too — and one of you will eventually get it wrong.
Do not bracket everything either. This is not an improvement:
print(((2) + ((3) * (4))))14Brackets that do not group anything a reader needed grouped are noise, and noise hides the brackets that matter.
The target is in between: bracket where the order is not obvious, leave it alone where it is.
7. Predict, Run, Observe
Predict each, then run.
print(2 + 3 * 4)
print((2 + 3) * 4)
print(2 ** 3 * 2)
print(10 - 4 - 3)
print(600 - 589 == 11)
print(True or False and False)
print((True or False) and False)
print(0b1010 & 0b0010 == 0b0010)
print(0b1010 & (0b0010 == 0b0010))14
20
16
3
True
True
False
True
0Two to check yourself on. Lines 6 and 7 differ because bracketing forced the or first. Lines 8 and 9 differ because the second one asks for the C grouping — the comparison is done first and its True is then used as a number.
8. Exercise
Exercise 1 — make it explicit. Each of these is correct. Add parentheses that change nothing but make the intent obvious to a reader:
(a) total - passed == 11 (b) error_count == 0 and finished (c) 2 + 3 * 4
Exercise 2 — the two groupings. Predict both, then run:
flags = 0b1100
print(flags & 0b0100 == 0b0100)
print(flags & (0b0100 == 0b0100))One is the bit test you meant. Say which, and which of the two a C programmer would have got by writing the first line.
Exercise 3 — predict the pair. Predict both, then run:
print(True or False and False)
print((True or False) and False)Say in one sentence which operator Python applied first in the unbracketed version.
Exercise 4 — the review question. A colleague submits this line. It is correct. Would you approve it as written, ask for brackets, or ask for it to be split across two lines? Justify in one sentence:
ready = error_count == 0 and coverage >= 95.0 and not timed_outExercise 5 — over-bracketed. Rewrite this so it says the same thing with only the brackets that help:
print((((600) - (589)) / (600)))9. Summary
- Precedence decides which operator is applied first when several appear in one expression. Python uses the conventional arithmetic order.
- Parentheses always win, and whatever is inside them is worked out first.
- Four rules cover almost everything: arithmetic before comparison; comparison before
and/or;notbeforeandbeforeor; and**before* / // %before+ -. - Bitwise operators bind tighter than comparisons — so
value & mask == xmasks first, as it looks. C groups it the other way round, so bracket it in both languages if you move between them. - Parentheses are a readability tool, not only an override. Add them where a reader would otherwise have to work out the order.
- Do not bracket everything. Noise hides the brackets that matter.
- Precedence puzzles are not a skill. An expression that needs a lookup should be rewritten, not decoded.
That completes Module 3 — Operators and Expressions. You can now build expressions from every operator family Python provides, predict both the value and the type they produce, reason about bits as well as quantities, and write expressions a colleague can read without stopping.
Everything so far has worked on one value at a time. The next module takes the value type you have used most — text — and shows what you can actually do with it: reaching individual characters, taking pieces out, searching, splitting a line into fields. Module 4 — Strings is where a line from a log file starts becoming something a script can take apart.
Where this fits
Part of the Python curriculum.
