This guide covers every operator from the source notebook, verified by running the actual code, with expanded coverage of the two sections the notebook leaves thinnest: the genuinely tricky is vs == distinction, and bitwise operators, which the notebook shows with zero explanation of what's actually happening at the bit level.
The Six Families
1. ARITHMETIC + - * / % // ** (math)
2. COMPARISON == != > < >= <= (True/False questions)
3. LOGICAL and or not (combining True/False)
4. ASSIGNMENT = += -= *= /= //= %= **= (storing values)
5. SPECIAL is / is not, in / not in (identity & membership)
6. BITWISE & | ^ (raw binary bits)
1. Arithmetic Operators
These perform the math you'd expect, with two symbols worth calling out specifically because they're easy to mix up.
num1 = 10
num2 = 20
num1 + num2 # 30 addition
num1 - num2 # -10 subtraction
num1 * num2 # 200 multiplication
num1 / 2 # 5.0 division: ALWAYS returns a float, even when it divides evenly
9 % 2 # 1 modulo: the REMAINDER after division
9 // 2 # 4 floor division: the whole-number result, rounded DOWN
8.5 ** 4.5 # 15218.966662792947 exponent (8.5 raised to the power 4.5)
The two people mix up most are % and //. They come from the same division, 9 / 2 = 4.5, but // keeps the whole-number part (4) and % keeps what's left over (1):
9 ÷ 2 = 4 remainder 1
9 // 2 → 4 (the whole part)
9 % 2 → 1 (the remainder)
4 × 2 + 1 = 9 ← this always holds: (a // b) * b + (a % b) == a
2. Comparison Operators
These compare two values and always produce a True or False.
| Operator | Meaning | With a = 3, b = 4 |
|---|---|---|
== |
Equal to | a == b → False |
!= |
Not equal to | a != b → True |
> |
Greater than | a > b → False |
< |
Less than | a < b → True |
>= |
Greater than or equal to | a >= b → False |
<= |
Less than or equal to | a <= b → True |
10 == 20 # False
10 != 20 # True
10 > 20 # False
10 < 20 # True
10 >= 10 # True
10 <= 10 # True
The one beginners genuinely trip on: = assigns a value, == asks a question about one. num1 = 10 stores 10 into num1. num1 == 10 checks whether num1 currently holds 10, and hands back True or False without changing anything.
3. Logical Operators
and, or, and not combine True/False values (or expressions that evaluate to them) into a single result.
num = 15
num < 10 # False
num > 0 # True
num < 10 and num > 0 # False: BOTH sides must be True; one of them isn't
num < 10 or num > 0 # True: only ONE side needs to be True
not num > 20 # True: flips the result (num > 20 is False, so 'not' makes it True)
AND: both must be True OR: at least one must be True
┌───────┬───────┬───────┐ ┌───────┬───────┬───────┐
│ A │ B │ A and B│ │ A │ B │ A or B │
├───────┼───────┼───────┤ ├───────┼───────┼───────┤
│ True │ True │ True │ │ True │ True │ True │
│ True │ False │ False │ │ True │ False │ True │
│ False │ True │ False │ │ False │ True │ True │
│ False │ False │ False │ │ False │ False │ False │
└───────┴───────┴───────┘ └───────┴───────┴───────┘
NOT simply flips whatever it's given: not True → False, not False → True
4. Assignment Operators
= stores a value. The rest (+=, -=, *=, /=, //=, %=, **=) are shortcuts that update a variable using its own current value, without typing the variable name twice.
num = 5
num = num + 5 # the long way
is exactly the same as:
num = 5
num += 5 # the short way, identical result
Here's every pair, each one starting fresh from num = 10 so the comparison is clean:
| Long form | Shortcut | Starting from num = 10 |
|---|---|---|
num = num + 5 |
num += 5 |
15 |
num = num - 2 |
num -= 2 |
8 |
num = num * 4 |
num *= 4 |
40 |
num = num / 5 |
num /= 5 |
2.0 |
num = num // 5 |
num //= 5 |
2 |
num = num % 3 |
num %= 3 |
1 |
num = num ** 10 |
num **= 10 |
10000000000 |
A word of caution if you're following along in a live notebook: if you run all of these cells one after another without resetting
numin between, each operator acts on whatever value the previous line left behind, not on a fresh10. The two forms still do the same thing to each other; it's just that the running total keeps changing underneath them. The table above resets tonum = 10for each row specifically so you can see the long form and the shortcut agree.
5. Special Operators
This is where the notebook's examples get genuinely subtle, so it's worth slowing down.
Identity Operators: is and is not
is doesn't ask "are these two values equal?" (that's ==). It asks "are these two names pointing at the exact same object in memory?" That distinction matters more than it looks like it should.
num1 = 10
num2 = 10
print(id(num1)) # e.g. 11755976
print(id(num2)) # the SAME number
num1 is num2 # True
That result, True, is real, but it's easy to misread as "Python always treats equal numbers as the same object." It doesn't. CPython (the standard Python implementation) pre-creates and reuses integer objects from -5 to 256 as an internal speed optimization. 10 falls inside that range, so both names happen to point at the same cached object. This is an implementation detail, not a language guarantee, and relying on it in real code is a mistake waiting to happen.
word1 = 'Python'
word2 = 'Python'
word1 is word2 # True: short, simple string literals are often "interned" (reused) too
Same story, different mechanism: CPython automatically reuses simple string literals that look like identifiers. It's another optimization, not a promise.
Now compare that with a list:
l1 = [10, 20, 30]
l2 = [10, 20, 30]
l1 is l2 # False: two separate objects, even though they hold identical values
Lists are never cached or reused this way. Two lists with identical contents are still two different boxes sitting at two different addresses. This is the version of is that matches most people's intuition, which is exactly why the integer and string cases above are worth flagging: the behavior isn't consistent across types, so is is not a safe way to compare values of any type.
l1 = [10, 20, 30] l2 = [10, 20, 30]
┌──────────────┐ ┌──────────────┐
│ Box A │ │ Box B │ Same VALUES,
│ [10, 20, 30] │ │ [10, 20, 30] │ different BOXES
└──────────────┘ └──────────────┘ l1 is l2 → False
Here's the case that actually matters day to day: what happens when you assign one variable to another?
l3 = [40, 50, 60]
l4 = l3 # NOT a copy: a second name for the SAME list
l3 is l4 # True
l3.append(70)
print(l3) # [40, 50, 60, 70]
print(l4) # [40, 50, 60, 70] (changed too, because there's only ONE list)
l3 ──┐
├──► [40, 50, 60, 70] ONE object, TWO labels.
l4 ──┘ Change it through either name,
and both "see" the change.
The rule of thumb: use == to compare values ("do these hold the same data?"). Use is only for identity checks where it's actually meaningful, most commonly x is None, x is True, or x is False, and when you specifically need to know whether two variable names refer to the exact same object in memory.
Membership Operators: in and not in
These check whether a value exists inside a sequence: a list, tuple, string, set, or dictionary.
list1 = [10, 20, 30, 40, 50, 60]
70 in list1 # False
70 not in list1 # True
6. Bitwise Operators
The notebook introduces these with no explanation beyond the symbols themselves, so this section builds that up from scratch. Bitwise operators don't work on numbers the way arithmetic does; they work on the individual bits (the 1s and 0s) that make up a number's binary representation.
A Quick Binary Refresher
Every integer is stored as a sequence of bits. To work through the examples, convert both numbers to binary first:
num1 = 12 → binary: 0 0 0 0 1 1 0 0
num2 = 25 → binary: 0 0 0 1 1 0 0 1
AND (&): 1 only where BOTH bits are 1
num1: 0 0 0 0 1 1 0 0
num2: 0 0 0 1 1 0 0 1
─────────────── & (compare each column)
result: 0 0 0 0 1 0 0 0 = 8
num1 & num2 → 8
OR (|): 1 where EITHER bit is 1
num1: 0 0 0 0 1 1 0 0
num2: 0 0 0 1 1 0 0 1
─────────────── |
result: 0 0 0 1 1 1 0 1 = 29
num1 | num2 → 29
XOR (^): 1 where the bits are DIFFERENT
num1: 0 0 0 0 1 1 0 0
num2: 0 0 0 1 1 0 0 1
─────────────── ^
result: 0 0 0 1 0 1 0 1 = 21
num1 ^ num2 → 21
num1 = 12
num2 = 25
num1 & num2 # 8
num1 | num2 # 29
num1 ^ num2 # 21
Bitwise operators show up far less often than arithmetic ones, but they're the backbone of things like permission flags (where each bit represents a yes/no switch), low-level networking code, and performance-critical bit-packing tricks. Two more you'll encounter but won't need immediately: ~ (flips every bit) and << / >> (shift all bits left or right, which is a fast way to multiply or divide by powers of 2).
Operator Precedence: Which Runs First?
When an expression mixes several operators, Python needs rules for what to calculate first, the same idea as PEMDAS in math class. From highest to lowest priority:
1. ** (exponent)
2. * / // % (multiplication, division, floor division, modulo)
3. + - (addition, subtraction)
4. == != > < >= <= (comparisons)
5. not
6. and
7. or
2 + 3 * 4 # 14, not 20: multiplication happens before addition
(2 + 3) * 4 # 20: parentheses override the default order
10 > 5 and 3 < 1 # False: comparisons resolve first (True and False), then 'and'
When in doubt, parentheses are free: wrapping the part you want evaluated first removes any ambiguity, for you and for anyone reading the code after you.
Quick Reference Summary
| Family | Operators | What it does |
|---|---|---|
| Arithmetic | + - * / % // ** |
Math: add, subtract, multiply, divide, remainder, floor divide, exponent |
| Comparison | == != > < >= <= |
Compares two values, always returns True/False |
| Logical | and or not |
Combines True/False values |
| Assignment | = += -= *= /= //= %= **= |
Stores or updates a variable's value |
| Identity | is is not |
Checks whether two names point at the SAME object (not just equal values) |
| Membership | in not in |
Checks whether a value exists inside a sequence |
| Bitwise | & \| ^ (also ~ << >>) |
Operates on individual binary bits |
The two ideas worth carrying forward from this guide: use == for values and reserve is for identity checks like x is None, since the two are not interchangeable. And when several operators appear in one expression, Python resolves them in a fixed order that mirrors ordinary math, with parentheses always available to make your intent explicit.
Operators are the verbs of Python. Once the six families stop feeling like an arbitrary list of symbols and start feeling like six different questions you can ask your data (how much? is it equal? are both true? which object is this? is it a member? what do the bits look like?), reading unfamiliar code gets a lot less intimidating.