This post builds on the intro post. If you have not read that one yet, start there first.
New to this topic? Start with: Order of Operations: What Runs First and Why It Matters
Associativity
Operator precedence tells you which operator runs first. Associativity tells you which direction to evaluate when two operators have the same priority.
Most operators in Python are left-to-right. Subtraction is a good example of why that matters:
10 - 3 - 2
Left-to-right gives you (10 - 3) - 2 = 5. Right-to-left would give you 10 - (3 - 2) = 9. Python uses left-to-right, so the answer is 5.
The one exception that surprises people is the exponent operator, which is right-to-left:
2 ** 3 ** 2
This evaluates as 2 ** (3 ** 2), which is 2 ** 9 = 512. Not (2 ** 3) ** 2 = 64. If you expected 64, you need parentheses.
Short-Circuit Evaluation
and and or do not just have a place in the precedence table. They also stop evaluating as soon as the result is certain.
With and, if the first condition is False, Python never checks the second one:
def slow_check():
print("running slow check")
return True
if False and slow_check():
pass
# "running slow check" never prints
With or, if the first condition is True, Python never checks the second:
if True or slow_check():
pass
# "running slow check" never prints
This is useful for protecting against errors:
items = []
# Safe - if items is empty, items[0] never runs
if items and items[0] == "admin":
print("admin found")
Without the short-circuit, items[0] on an empty list would raise an IndexError.
Integer Division and Type Mixing
Python 3 has two division operators:
10 / 3 # 3.3333... float division
10 // 3 # 3 integer division, rounds toward negative infinity
10 % 3 # 1 remainder
Modulo has some non-obvious behavior with negative numbers that is worth knowing, especially if you have a C or JavaScript background. A dedicated post on modulo is coming soon.
When you mix integers and floats, the result is always a float:
5 + 2.0 # 7.0
10 // 3.0 # 3.0 still integer division, but returns a float
A common bug with percentages:
score = 450
total = 1000
# Wrong in Python 2, correct in Python 3
percent = score / total * 100 # 45.0
# If you want an integer result
percent = score * 100 // total # 45
Order matters here. score / total gives 0.45 first, then multiplied by 100 gives 45.0. If you wrote score / (total * 100) you would get 0.0045.
Chained Comparisons
Python lets you chain comparisons in a way most languages do not:
x = 5
1 < x < 10 # True
Python reads this as (1 < x) and (x < 10). In JavaScript or C# you would have to write that out explicitly:
x > 1 && x < 10
Chained comparisons work with any mix of operators:
0 <= score <= 100 # checks both bounds at once
a < b < c < d # checks all three in sequence
Bitwise Operators
Bitwise operators work directly on the binary representation of integers. They come up regularly in game development, systems programming, and permission flags.
5 & 3 # 1 AND - bits that are set in both
5 | 3 # 7 OR - bits that are set in either
5 ^ 3 # 6 XOR - bits that are set in one but not both
~5 # -6 NOT - flips all bits
5 << 1 # 10 left shift - multiply by 2
5 >> 1 # 2 right shift - divide by 2
Bitwise operators sit between arithmetic and comparison in the precedence table, which leads to this common mistake:
x = 5
if x & 3 == 1: # reads as x & (3 == 1), which is x & False, which is 0
print("match")
The fix:
if (x & 3) == 1:
print("match")
A Game Development Example
Score calculation in a game:
kills = 5
kill_points = 10
headshot_bonus = 25
multiplier = 2
# Intent: (kills * kill_points + headshot_bonus) * multiplier
score = kills * kill_points + headshot_bonus * multiplier
# Actual: 50 + 50 = 100
score = (kills * kill_points + headshot_bonus) * multiplier
# Correct: (50 + 25) * 2 = 150
The first version compiles and runs without any error. It just gives you the wrong number. These are the hardest bugs to find because nothing breaks.
Language Differences
| Feature | Python | JavaScript | C# |
|---|---|---|---|
| Exponent | ** |
** |
Math.Pow() |
| Integer division | // |
Math.floor(a/b) |
(int)(a/b) |
| Chained comparisons | 1 < x < 10 |
not supported | not supported |
| Strict equality | == (always strict) |
=== |
== |
| Bitwise NOT | ~x |
~x |
~x |
JavaScript == performs type coercion, which is why 0 == false returns true there. Python == does not coerce types that way.
Excel: Nested Functions
When you nest functions in Excel, the innermost function runs first, then works outward:
=ROUND(SUM(A1:A5) / COUNT(A1:A5), 2)
Order: SUM runs, COUNT runs, division happens, then ROUND applies. Standard order of operations applies inside the arguments too:
=SUM(A1*2, B1+C1) # multiplies A1 by 2, adds B1 and C1, then sums both
References
- Python operator precedence
- MDN: JavaScript operator precedence
- Excel calculation operators and precedence
- Math for Programmers by Paul Orland (Manning, 2021)
Ran into a case not covered here? Post it below and we will work through it.
New to this topic? Start with: Order of Operations: What Runs First and Why It Matters