Python Operators
Learn Python's arithmetic, comparison, logical, bitwise, and walrus operators with practical examples.
Arithmetic Operators
Arithmetic operators perform the numeric computations you’d expect, with a few Python-specific behaviors worth knowing: / always returns a float, ** is right-associative, and % follows the sign of the divisor (not the dividend) for negative numbers.
a, b = 10, 3
a + b # 13 — addition
a - b # 7 — subtraction
a * b # 30 — multiplication
a / b # 3.3333… — true division: always returns a float
a // b # 3 — floor division: rounds toward negative infinity
a % b # 1 — modulo: remainder after floor division
a ** b # 1000 — exponentiation
# Augmented assignment operators — modify in place
x = 5
x += 3 # x = 8
x *= 2 # x = 16
x //= 3 # x = 5
x **= 2 # x = 25
Operator Precedence (High to Low)
Operator precedence determines how expressions are parsed when no parentheses are used. In practice, use parentheses to make your intent explicit — relying on memorized precedence rules makes code harder to read and review.
** exponentiation (right-associative)
+x, -x, ~x unary operators
*, /, //, % multiplication, division
+, - addition, subtraction
<<, >> bitwise shift
& bitwise AND
^ bitwise XOR
| bitwise OR
==, !=, <, >, <=, >=, is, is not, in, not in
not logical NOT
and logical AND
or logical OR
:= walrus
Comparison Operators
Comparison operators return a boolean and are the backbone of every conditional statement. Python supports chained comparisons — a feature that reads naturally and avoids repeating the variable name.
5 == 5 # True
5 != 4 # True
5 > 3 # True
5 < 3 # False
5 >= 5 # True
5 <= 4 # False
# Chained comparisons — very Pythonic, equivalent to using 'and'
x = 7
1 < x < 10 # True — same as (1 < x) and (x < 10)
0 <= x <= 5 # False
# Equivalent long form:
1 < x and x < 10
Identity vs Equality
== checks whether two objects have equal values. is checks whether they are the exact same object in memory. The distinction matters most with mutable objects and None.
a = [1, 2, 3]
b = [1, 2, 3]
c = a
a == b # True — same values
a is b # False — different list objects, even though values match
a is c # True — c is another name for the same object as a
# Correct None check — always use 'is' because None is a singleton
value = None
value is None # True (correct — checks identity)
value == None # True (works, but misleading — use 'is')
Logical Operators
Logical operators combine boolean expressions. They return one of their operands — not necessarily True or False — which enables a useful set of idioms for default values.
True and False # False
True or False # True
not True # False
Short-Circuit Evaluation
Python evaluates and/or lazily, stopping as soon as the result is determined. This means the right-hand side of an and is never evaluated if the left is falsy, and the right-hand side of an or is never evaluated if the left is truthy. This has real performance and safety implications.
# 'and' stops at the first falsy value — expensive_function() never runs
False and expensive_function()
# 'or' stops at the first truthy value — expensive_function() never runs
True or expensive_function()
# Practical use: provide default values without if/else
name = user_input or "Anonymous" # use "Anonymous" if user_input is empty
config = provided_config or load_default() # only call load_default() if needed
Truthiness in Conditions
Python evaluates any object as truthy or falsy in a boolean context, not just explicit True/False. Writing if items: instead of if len(items) > 0: is idiomatic Python and slightly faster.
items = []
if not items:
print("Empty list") # idiomatic — leverages truthiness
user = None
if user is None: # use 'is' for None, not truthiness
print("Not logged in")
# or-assignment: assign a default if the left side is falsy
settings = user_settings or {}
Membership Operators
Membership operators test whether a value exists inside a collection. They work across all sequence and collection types — but the underlying time complexity differs significantly by type.
"a" in "alphabet" # True — substring check
"z" in "alphabet" # False
"z" not in "alphabet" # True
3 in [1, 2, 3, 4] # True — list scan: O(n)
5 not in {1, 2, 3} # True — set lookup: O(1)
# Dict membership checks keys, not values
"name" in {"name": "Alice", "age": 30} # True — "name" is a key
"Alice" in {"name": "Alice"} # False — "Alice" is a value, not a key
in on a set or dict is O(1) because they’re backed by hash tables. On a list, it’s O(n) because Python scans every element. When you’re doing many membership checks, convert to a set first.
Bitwise Operators
Bitwise operators work directly on the binary representation of integers. They’re essential for low-level programming tasks: permission flags, network masks, hardware register manipulation, and compact storage of boolean fields.
a = 0b1010 # 10
b = 0b1100 # 12
a & b # 0b1000 = 8 — AND: bits set in both a and b
a | b # 0b1110 = 14 — OR: bits set in either a or b
a ^ b # 0b0110 = 6 — XOR: bits set in one but not both
~a # -11 — NOT: inverts all bits (two's complement)
a << 1 # 20 — left shift: equivalent to multiplying by 2
a >> 1 # 5 — right shift: equivalent to integer division by 2
Practical Bitwise Use: Permission Flags
Using a single integer as a compact set of boolean flags is a pattern you’ll see in OS APIs, file systems, and network protocols. Each bit represents one permission, and you can combine or test permissions with a single operation.
READ = 0b001 # 1
WRITE = 0b010 # 2
EXECUTE = 0b100 # 4
# Grant read and write by OR-ing the flags together
permissions = READ | WRITE # 0b011 = 3
# Test a permission with AND — non-zero means the bit is set
has_read = bool(permissions & READ) # True
has_execute = bool(permissions & EXECUTE) # False
# Revoke write by AND-ing with the bitwise NOT of WRITE
permissions &= ~WRITE # 0b001 = 1 — write bit cleared
The Walrus Operator (:=)
Introduced in Python 3.8, := assigns a value to a variable and returns that value in the same expression. It solves a specific problem: when you need to compute a value, check it, and then use it — without computing it twice or writing extra setup code.
In a while Loop
# Without walrus — reads chunk, then checks chunk separately
while True:
chunk = file.read(1024)
if not chunk:
break
process(chunk)
# With walrus — read and check in the same expression
while chunk := file.read(1024):
process(chunk)
In Comprehensions
import re
data = ["[email protected]", "invalid", "[email protected]", "bad"]
# Without walrus — the regex runs twice for each match: once to filter, once to use
valid = [m.group() for s in data if re.match(r"\w+@\w+\.\w+", s)
for m in [re.match(r"\w+@\w+\.\w+", s)]]
# With walrus — compute once, use in both the condition and the value
valid = [m.group() for s in data if (m := re.match(r"\w+@\w+\.\w+", s))]
# ["[email protected]", "[email protected]"]
In if Statements
import json
raw = '{"name": "Alice", "age": 30}'
# Parse and use the result without a separate assignment line
if data := json.loads(raw):
print(f"Loaded: {data['name']}")
Ternary (Conditional) Expression
Python’s inline conditional expression provides a compact way to express simple if/else logic in a single line. It reads like natural English: “value if condition else other-value.”
x = 10
label = "even" if x % 2 == 0 else "odd" # "even"
# Nested ternary — use sparingly, it hurts readability quickly
grade = "A" if score >= 90 else "B" if score >= 80 else "C"
Operator Overloading
Python lets you define how operators behave on your own classes by implementing special dunder methods. This is how NumPy arrays support + and *, how Pandas DataFrames support ==, and how custom types integrate naturally with Python’s syntax.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
# Called when you write v1 + v2
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v1 + v2 # Vector(4, 6) — __add__ is called automatically
Key dunder methods for operators: __add__, __sub__, __mul__, __truediv__, __eq__, __lt__, __len__, __contains__.