Operators are Python's action words. You cannot write a meaningful program without them. This guide teaches every operator category by building a single, working Python program from scratch — a small store inventory tracker. Each section adds new lines to that program using the operators being covered, so you see them doing actual work rather than sitting in isolated one-liners.
By the end of this article you will have a complete, runnable script. Every code block below is a piece of that script — copy them in order into a single .py file and it will run. The output blocks show exactly what each section prints.
The Program We Are Building
The program tracks inventory for a small store. It starts with a list of products, their stock counts, and their prices. Over the course of this guide we will calculate totals, apply discounts, flag low stock, filter products, and encode status flags — one operator category at a time. Here is the initial data setup:
# inventory_tracker.py — initial data
# Each product: [name, stock_count, unit_price]
products = [
["Widget A", 42, 4.99],
["Widget B", 7, 12.50],
["Gadget X", 0, 89.00],
["Gadget Y", 18, 34.75],
["Doohickey", 3, 2.25],
]
TAX_RATE = 0.08 # 8%
LOW_STOCK = 10 # threshold for low-stock warning
DISCOUNT_RATE = 0.15 # 15% bulk discount
Nothing runs yet — we have just defined our data. Every section that follows adds code below this block.
Arithmetic Operators
Python has seven arithmetic operators. Four are familiar from math class. Three are worth spending extra time on: floor division //, modulo %, and exponentiation **.
| Operator | Name | Notes |
|---|---|---|
+ | Addition | Also concatenates strings and lists |
- | Subtraction | Also works as unary negation: -x |
* | Multiplication | Also repeats strings and lists |
/ | Division | Always returns float in Python 3 |
// | Floor division | Truncates toward negative infinity, not toward zero |
% | Modulo | Remainder after division — sign follows the divisor in Python |
** | Exponentiation | Right-associative: 2**3**2 = 2**(3**2) = 512 |
We will use arithmetic operators to calculate the total value of inventory, apply tax, and figure out how many full crates of 12 each product fills (using floor division and modulo together).
# ── Arithmetic operators ──────────────────────────────────
CRATE_SIZE = 12
print("=== Inventory Value Report ===")
for name, stock, price in products:
line_value = stock * price # multiplication
taxed_value = line_value + (line_value * TAX_RATE) # addition
full_crates = stock // CRATE_SIZE # floor division
loose_units = stock % CRATE_SIZE # modulo (remainder)
storage_area = stock ** (1/3) # cube root via fractional exponent
print(f" {name:<12} stock={stock:>3} "
f"value=${line_value:>7.2f} "
f"taxed=${taxed_value:>7.2f} "
f"crates={full_crates} + {loose_units} loose")
=== Inventory Value Report ===
Widget A stock= 42 value=$ 209.58 taxed=$ 226.35 crates=3 + 6 loose
Widget B stock= 7 value=$ 87.50 taxed=$ 94.50 crates=0 + 7 loose
Gadget X stock= 0 value=$ 0.00 taxed=$ 0.00 crates=0 + 0 loose
Gadget Y stock= 18 value=$ 625.50 taxed=$ 675.54 crates=1 + 6 loose
Doohickey stock= 3 value=$ 6.75 taxed=$ 7.29 crates=0 + 3 looseFloor division rounds toward negative infinity, not toward zero. 7 // 2 is 3, but -7 // 2 is -4 — not -3. If you need truncation toward zero, use int(-7 / 2) instead. For inventory counts this never matters, but it bites people writing date/time arithmetic.
Augmented Assignment Operators
Augmented assignment operators collapse a read-modify-write into one line. Every arithmetic and bitwise operator has an augmented form: += -= *= /= //= %= **= &= |= ^= <<= >>=.
One important subtlety: for mutable types like lists, x += [item] mutates the list in place (same as x.extend([item])). For immutable types like integers and strings, it creates a new object and rebinds the name. This distinction matters when multiple variables point at the same object.
# ── Augmented assignment operators ────────────────────────
# Simulate receiving a shipment and selling stock
total_revenue = 0.0
total_units_sold = 0
# Shipment arrives: +20 units of Widget B, +15 of Gadget X
products[1][1] += 20 # Widget B stock: 7 + 20 = 27
products[2][1] += 15 # Gadget X stock: 0 + 15 = 15
# Price increase: Gadget X goes up 10%
products[2][2] *= 1.10 # 89.00 * 1.10 = 97.90
# Process some sales
sales = [("Widget A", 5), ("Gadget X", 3), ("Doohickey", 1)]
for sale_name, qty in sales:
for product in products:
if product[0] == sale_name:
revenue = qty * product[2]
product[1] -= qty # reduce stock
total_revenue += revenue # accumulate revenue
total_units_sold += qty # accumulate units
print(f"\nAfter shipment and sales:")
print(f" Total revenue: ${total_revenue:.2f}")
print(f" Total units sold: {total_units_sold}")
# Show updated stock
for name, stock, price in products:
print(f" {name:<12} stock now = {stock}")
After shipment and sales:
Total revenue: $318.20
Total units sold: 9
Widget A stock now = 37
Widget B stock now = 27
Gadget X stock now = 12
Gadget Y stock now = 18
Doohickey stock now = 2+= on a string builds a new string object every time because strings are immutable. Inside a loop this generates a lot of garbage. If you are accumulating many strings, collect them in a list and join at the end: "".join(parts) is dramatically faster for large inputs.
Comparison Operators
Comparison operators evaluate a relationship between two values and return True or False. Python supports six: == != > < >= <=. They also chain — 0 <= stock < 10 is a single expression that checks both bounds at once.
We will use comparisons to classify each product's stock status and to find anything that needs reordering.
# ── Comparison operators ──────────────────────────────────
REORDER_POINT = 5
print("\n=== Stock Status ===")
for name, stock, price in products:
# Chained comparison: checks both bounds in one expression
if 0 < stock <= LOW_STOCK:
status = "LOW"
elif stock == 0:
status = "OUT"
elif stock > LOW_STOCK:
status = "OK"
else:
status = "UNKNOWN"
needs_reorder = stock <= REORDER_POINT # boolean into a variable
flag = " ** REORDER **" if needs_reorder else ""
print(f" {name:<12} stock={stock:>3} status={status:<7}{flag}")
# Find the most expensive in-stock product
# Comparison used to drive a running maximum
most_expensive_name = None
most_expensive_price = 0.0
for name, stock, price in products:
if stock > 0 and price > most_expensive_price:
most_expensive_price = price
most_expensive_name = name
print(f"\n Most expensive in-stock: {most_expensive_name} (${most_expensive_price:.2f})")
=== Stock Status ===
Widget A stock= 37 status=OK
Widget B stock= 27 status=OK
Gadget X stock= 12 status=OK
Gadget Y stock= 18 status=OK
Doohickey stock= 2 status=LOW ** REORDER **
Most expensive in-stock: Gadget X ($97.90)Never use == to compare floating-point numbers for exact equality. 0.1 + 0.2 == 0.3 returns False in Python because of how binary floating-point works. Use abs(a - b) < 1e-9 or math.isclose(a, b) when you need float equality. For money calculations, use the decimal module instead of floats.
Logical Operators and Short-Circuiting
Python's logical operators — and or not — combine boolean expressions. What surprises many beginners is that they do not return True or False directly. They return one of their operands. This makes them useful for more than just if statements.
# ── Logical operators + short-circuit evaluation ──────────
print("\n=== Discount Eligibility ===")
# Bulk discount: in stock AND price over $10 AND quantity over 15
for name, stock, price in products:
eligible = stock > 0 and price > 10.00 and stock > 15
if eligible:
discounted = price * (1 - DISCOUNT_RATE)
print(f" {name:<12} ${price:.2f} -> ${discounted:.2f} (bulk discount applied)")
# 'or' for safe default — if name is falsy, fall back to "Unknown"
override_name = ""
display_name = override_name or "Unknown Product"
print(f"\n Fallback name demo: '{display_name}'")
# 'not' to invert a flag
out_of_stock_items = [name for name, stock, _ in products if not stock > 0]
print(f" Out-of-stock items: {out_of_stock_items or 'none'}")
# Short-circuit: right side never runs if left side settles it
def expensive_check():
print(" (expensive_check ran)")
return True
flag = False and expensive_check() # expensive_check() is never called
print(f"\n Short-circuit with 'and': flag={flag} (no call above)")
flag = True or expensive_check() # expensive_check() is never called
print(f" Short-circuit with 'or' : flag={flag} (no call above)")
=== Discount Eligibility ===
Widget B $12.50 -> $10.62 (bulk discount applied)
Gadget Y $34.75 -> $29.54 (bulk discount applied)
Fallback name demo: 'Unknown Product'
Out-of-stock items: none
Short-circuit with 'and': flag=False (no call above)
Short-circuit with 'or' : flag=True (no call above)The short-circuit behavior matters in real code. When the left side of and is False, Python skips the right side entirely — the result cannot change. This means you can safely guard against errors like x is not None and x.value > 0; if x is None, the attribute access on the right never runs.
# Safe attribute access via short-circuit guard
class Product:
def __init__(self, name, stock):
self.name = name
self.stock = stock
p = None
# Without guard this would raise AttributeError:
# print(p.stock > 0)
# With short-circuit guard — safe:
has_stock = p is not None and p.stock > 0
print(f"\n Safe guard result: {has_stock}")
p = Product("Gadget Z", 5)
has_stock = p is not None and p.stock > 0
print(f" With real object: {has_stock}")
Safe guard result: False
With real object: TrueIdentity and Membership Operators
is and is not check object identity — whether two names point to the same object in memory, not whether their values are equal. in and not in check whether a value exists inside a container.
# ── Identity operators (is / is not) ──────────────────────
print("\n=== Identity vs Equality ===")
a = [1, 2, 3]
b = [1, 2, 3]
c = a # c points at the same list object as a
print(f" a == b : {a == b} (same values)")
print(f" a is b : {a is b} (different objects in memory)")
print(f" a is c : {a is c} (same object — c is an alias)")
# Mutating via alias — this is why 'is' matters
c.append(4)
print(f" After c.append(4): a = {a}") # a changed too!
# Correct use of 'is': checking for None
def find_product(target_name):
for product in products:
if product[0] == target_name:
return product
return None # explicit sentinel value
result = find_product("Widget A")
if result is not None: # correct None check
print(f"\n Found: {result[0]}, stock={result[1]}")
result = find_product("Widget Z")
if result is None:
print(f" Widget Z not found (result is None: {result is None})")
=== Identity vs Equality ===
a == b : True (same values)
a is b : False (different objects in memory)
a is c : True (same object — c is an alias)
After c.append(4): a = [1, 2, 3, 4]
Found: Widget A, stock=37
Widget Z not found (result is None: True)# ── Membership operators (in / not in) ────────────────────
print("\n=== Membership Checks ===")
product_names = [p[0] for p in products]
premium_names = {"Gadget X", "Gadget Y"} # set — O(1) lookup
search_targets = ["Widget A", "Widget Z", "Gadget X"]
for target in search_targets:
in_inventory = target in product_names # list — O(n) scan
is_premium = target in premium_names # set — O(1) lookup
print(f" {target:<12} in_inventory={in_inventory} is_premium={is_premium}")
# 'in' on a string checks for substrings
keyword = "Gadget"
gadgets = [name for name in product_names if keyword in name]
print(f"\n Products matching '{keyword}': {gadgets}")
# 'not in' in a guard
restricted = {"Gadget X"}
print("\n Sale eligibility:")
for name, stock, price in products:
if name not in restricted and stock > 0:
print(f" {name} is available for sale")
=== Membership Checks ===
Widget A in_inventory=True is_premium=False
Widget Z in_inventory=False is_premium=False
Gadget X in_inventory=True is_premium=True
Products matching 'Gadget': ['Gadget X', 'Gadget Y']
Sale eligibility:
Widget A is available for sale
Widget B is available for sale
Gadget Y is available for sale
Doohickey is available for saleBitwise Operators
Bitwise operators manipulate numbers at the binary level. They are used in systems programming, networking, cryptography, and anywhere compact flag storage is needed. The six operators are: & (AND) | (OR) ^ (XOR) ~ (NOT) << (left shift) >> (right shift).
We will use a bitfield to encode multiple boolean properties of each product into a single integer — a technique common in low-level code, protocol parsing, and database flag columns.
# ── Bitwise operators — product status flags ──────────────
# We encode four boolean properties into a single integer.
# Each property occupies one bit position (a power of 2).
FLAG_IN_STOCK = 1 << 0 # bit 0 = 0b0001 = 1
FLAG_LOW_STOCK = 1 << 1 # bit 1 = 0b0010 = 2
FLAG_PREMIUM = 1 << 2 # bit 2 = 0b0100 = 4
FLAG_ON_SALE = 1 << 3 # bit 3 = 0b1000 = 8
print(f"\n Flag values (decimal): IN_STOCK={FLAG_IN_STOCK} "
f"LOW={FLAG_LOW_STOCK} PREMIUM={FLAG_PREMIUM} SALE={FLAG_ON_SALE}")
sale_items = {"Widget B", "Gadget Y"}
premium_set = {"Gadget X", "Gadget Y"}
print("\n=== Bitfield Status Encoding ===")
status_map = {}
for name, stock, price in products:
flags = 0 # start with no flags set
if stock > 0:
flags |= FLAG_IN_STOCK # set bit 0 with OR
if 0 < stock <= LOW_STOCK:
flags |= FLAG_LOW_STOCK # set bit 1
if name in premium_set:
flags |= FLAG_PREMIUM # set bit 2
if name in sale_items:
flags |= FLAG_ON_SALE # set bit 3
status_map[name] = flags
print(f" {name:<12} flags={flags:>2} binary={flags:04b}")
# Now read the flags back using AND to test individual bits
print("\n=== Decoding Flags ===")
for name, flags in status_map.items():
in_stock = bool(flags & FLAG_IN_STOCK) # is bit 0 set?
low = bool(flags & FLAG_LOW_STOCK) # is bit 1 set?
premium = bool(flags & FLAG_PREMIUM) # is bit 2 set?
on_sale = bool(flags & FLAG_ON_SALE) # is bit 3 set?
attrs = []
if in_stock : attrs.append("in-stock")
if low : attrs.append("LOW")
if premium : attrs.append("premium")
if on_sale : attrs.append("on-sale")
print(f" {name:<12} {', '.join(attrs) or 'out of stock'}")
# XOR to toggle the ON_SALE flag for Widget A
name = "Widget A"
status_map[name] ^= FLAG_ON_SALE
print(f"\n After toggling ON_SALE for Widget A: flags={status_map[name]:04b}")
status_map[name] ^= FLAG_ON_SALE # toggle back
print(f" After toggling again: flags={status_map[name]:04b}")
# Right shift to divide by powers of 2 efficiently
bulk_price = 97.90
half_price = int(bulk_price * 100) >> 1 # divide cents integer by 2
print(f"\n Integer right-shift halving: {int(bulk_price * 100)} >> 1 = {half_price} cents")
Flag values (decimal): IN_STOCK=1 LOW=2 PREMIUM=4 SALE=8
=== Bitfield Status Encoding ===
Widget A flags= 1 binary=0001
Widget B flags= 9 binary=1001
Gadget X flags= 5 binary=0101
Gadget Y flags=13 binary=1101
Doohickey flags= 3 binary=0011
=== Decoding Flags ===
Widget A in-stock
Widget B in-stock, on-sale
Gadget X in-stock, premium
Gadget Y in-stock, premium, on-sale
Doohickey in-stock, LOW
After toggling ON_SALE for Widget A: flags=1001
After toggling again: flags=0001
Integer right-shift halving: 9790 >> 1 = 4895 cents
Since Python 3.9, the | operator also merges two dictionaries: merged = dict_a | dict_b. This is separate from the bitwise OR behavior — Python checks the operand types and routes to the right operation. The augmented form dict_a |= dict_b updates in place.
The Walrus Operator
The walrus operator := was introduced in Python 3.8. It assigns a value and returns it in the same expression. The name comes from the way it looks — the colon and equals together resemble a walrus face sideways. Its formal name is the assignment expression operator.
It is most useful in two places: while loops where you need to capture a computed value and test it in one step, and list comprehensions where computing a value for both the condition and the output would otherwise require computing it twice.
# ── Walrus operator (:=) ──────────────────────────────────
import math
print("\n=== Walrus Operator Examples ===")
# Example 1: while loop — read commands until quit
commands = ["check Widget A", "check Gadget Y", "quit", "check Widget B"]
idx = 0
while (cmd := commands[idx]) != "quit":
# cmd is already assigned — no need to re-read
parts = cmd.split()
search = " ".join(parts[1:])
for name, stock, price in products:
if name == search:
print(f" [{cmd}] -> stock={stock}, price=${price:.2f}")
idx += 1
print(f" Stopped at command: '{cmd}'")
# Example 2: comprehension — compute a value once, use it twice
# Without walrus you would compute len(name) twice:
# [name for name in product_names if len(name) > 8]
# With walrus:
long_names = [
(name, n)
for name in product_names
if (n := len(name)) > 8
]
print(f"\n Products with name longer than 8 chars:")
for name, length in long_names:
print(f" '{name}' ({length} chars)")
# Example 3: find the first product under $5 using a while loop
idx = 0
while idx < len(products) and (p := products[idx])[2] >= 5.00:
idx += 1
if idx < len(products):
print(f"\n First product under $5: {p[0]} (${p[2]:.2f})")
=== Walrus Operator Examples ===
[check Widget A] -> stock=37, price=$4.99
[check Gadget Y] -> stock=18, price=$34.75
Stopped at command: 'quit'
Products with name longer than 8 chars:
'Doohickey' (9 chars)
First product under $5: Widget A ($4.99)The walrus operator is useful in loops and comprehensions, but overusing it inside regular expressions makes code harder to read. If a plain assignment on its own line is clearer, use that. The walrus operator is a tool for specific situations — not a shorthand to apply everywhere.
Operator Precedence and the Full Program
When an expression contains multiple operators, Python follows a strict evaluation order. Expressions in parentheses are always evaluated first. After that, the order from highest to lowest precedence is:
| Priority | Operators |
|---|---|
| 1 — highest | () — parentheses |
| 2 | ** — exponentiation (right-associative) |
| 3 | +x -x ~x — unary operators |
| 4 | * / // % |
| 5 | + - |
| 6 | << >> — bitwise shifts |
| 7 | & — bitwise AND |
| 8 | ^ — bitwise XOR |
| 9 | | — bitwise OR |
| 10 | == != > < >= <= is is not in not in |
| 11 | not — logical NOT |
| 12 | and — logical AND |
| 13 — lowest | or — logical OR |
A practical example where precedence produces surprising results — and where parentheses fix it:
# ── Precedence in practice ────────────────────────────────
stock = 7
price = 12.50
# Intended: (stock > 0) and (price > 10)
# Without parens — works as intended because comparison > and
result_a = stock > 0 and price > 10
print(f"\n stock > 0 and price > 10 -> {result_a}")
# Tricky: mixing bitwise and comparison — always use parens here
flags = 0b0101 # IN_STOCK + PREMIUM
is_ok = flags & FLAG_IN_STOCK != 0 # WRONG: parsed as flags & (FLAG_IN_STOCK != 0)
is_ok2 = (flags & FLAG_IN_STOCK) != 0 # CORRECT
print(f" Without parens (wrong): {is_ok}")
print(f" With parens (correct): {is_ok2}")
# Exponentiation is right-associative — matters more than you'd think
print(f"\n 2 ** 3 ** 2 = {2 ** 3 ** 2}") # 2**(3**2) = 2**9 = 512
print(f" (2 ** 3) ** 2 = {(2 ** 3) ** 2}") # 8**2 = 64
# Final summary report using multiple operator types together
print("\n=== Final Inventory Summary ===")
total_value = sum(s * p for _, s, p in products)
total_taxed = total_value * (1 + TAX_RATE)
in_stock_qty = sum(s for _, s, _ in products if s > 0)
product_count = len(products)
avg_price = sum(p for _, _, p in products) / product_count
print(f" Products tracked: {product_count}")
print(f" Total units: {in_stock_qty}")
print(f" Inventory value: ${total_value:.2f}")
print(f" With tax ({TAX_RATE*100:.0f}%): ${total_taxed:.2f}")
print(f" Avg unit price: ${avg_price:.2f}")
print(f" Low stock items: {sum(1 for _, s, _ in products if 0 < s <= LOW_STOCK)}")
stock > 0 and price > 10 -> True
Without parens (wrong): True
With parens (correct): True
2 ** 3 ** 2 = 512
(2 ** 3) ** 2 = 64
=== Final Inventory Summary ===
Products tracked: 5
Total units: 96
Inventory value: $1261.13
With tax (8%): $1362.02
Avg unit price: $28.09
Low stock items: 1"When code and comments disagree, both are probably wrong." — Norm Schryer
The same principle applies to operator precedence: when you are not certain which sub-expression evaluates first, add parentheses. They make your intent explicit and protect the code against misreading — by you, by colleagues, and by future versions of yourself six months from now.
Key Takeaways
- Division always returns a float: Use
//when you need an integer result. Pair it with%to extract both the quotient and the remainder together. - Augmented assignment (
+=etc.) is not purely syntactic sugar: For mutable types like lists it mutates in place; for immutable types it rebinds the name. Know which you are working with. - Logical operators return values, not booleans:
oris the standard Python idiom for defaults (x or "fallback"). Short-circuit evaluation means the right side can be a guard, not just a value. - Use
isonly forNone,True, andFalse: For everything else use==. The small-integer cache makesisappear to work on numbers in the interpreter, but it is not guaranteed. - Bitwise operators are about binary representations: Bitfields let you store many boolean flags in a single integer and test or toggle them individually using
&,|, and^. - Add parentheses whenever you mix bitwise and comparison operators:
(flags & MASK) != 0is safer and clearer than relying on precedence rules that most readers will not recall under pressure. - The walrus operator (
:=) has two natural homes:whileloops where you need to capture and test in one step, and comprehensions where an intermediate value would otherwise be computed twice.
You now have a complete, runnable program that exercises every operator category Python provides. Paste the code blocks in order into a single file and run it — the output should match every section above. From here, the next natural step is Python's control flow structures, where these operators do their most important work.