§4  Operators
Python Programming Series  ·  Article 4

Operators in Python: A Complete Guide

Every calculation, comparison, and condition in your code comes down to operators: the symbols that take one or more values (called operands) and produce a result. +, ==, and, is, &, these are all operators, and Python groups them into six families based on what they do.

All 6 operator families
is vs == explained properly
Bitwise, bit by bit
Operator precedence

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.

python
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
python
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.

python
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.

python
num = 5
num = num + 5   # the long way

is exactly the same as:

python
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 num in between, each operator acts on whatever value the previous line left behind, not on a fresh 10. 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 to num = 10 for 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.

python
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.

python
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:

python
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?

python
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.

python
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
python
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
python
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.