Assignment & Comparison Operators
Master the foundational operators that power every Python program - from simple variable assignments to complex logical comparisons that drive decision-making in your code.
Operators are the building blocks of any programming language, and Python's operator system is both elegant and powerful. In this chapter, we'll explore two critical categories: assignment operators, which store and update values in memory, and comparison operators, which evaluate relationships between values to produce boolean results. Understanding these operators isn't just about memorizing syntax—it's about developing an intuition for how Python manipulates data at a fundamental level.
Whether you're building a simple calculator, processing user input, or implementing complex business logic, you'll use these operators constantly. By the end of this chapter, you'll understand not just the "what" and "how," but also the "why" behind Python's operator design choices, including common pitfalls that trip up even experienced developers.
What You'll Learn
- How assignment operators work with Python's reference model
- All compound assignment operators and their performance implications
- Comparison operators and the critical difference between
==andis - Common mistakes that cause bugs in production code
- Best practices for writing clean, readable comparisons
Assignment Operators: Storing Values in Memory
The assignment operator (=) is your primary tool for storing and updating values in Python. Unlike mathematical equality, assignment is a directive: "Take the value on the right and bind it to the name on the left." This distinction is crucial because Python variables are references to objects, not containers that hold values directly.
# Simple assignment
x = 10 # x now references the integer object 10
name = "Alice" # name references the string object "Alice"
is_valid = True # is_valid references the boolean True
# Multiple assignment
a, b, c = 1, 2, 3 # Assigns 1 to a, 2 to b, 3 to c
x = y = z = 0 # All three reference the same integer 0When you write x = 10, Python creates (or reuses) an integer object with value 10 in memory, then creates a binding in the current namespace from the name "x" to that object. This reference model has important implications for how variables behave, especially with mutable objects like lists and dictionaries.
b = a), both variables reference the same object in memory. This matters significantly with mutable objects.Advanced Assignment: Unpacking Power
One of Python's most beloved features is "Unpacking" (also known as Destructuring). It allows you to assign multiple variables from a single iterable in one clean line.
1. Tuple Unpacking & Swapping
Why write three lines to swap variables when you can write one?
# The Old Way (C/Java style)
temp = a
a = b
b = temp
# The Python Way
a, b = b, a # Swaps values instanty!
# Unpacking a tuple
coordinates = (10, 20)
x, y = coordinates # x=10, y=202. Extended Iterable Unpacking (*)
Introduced in Python 3, the asterisk (*) operator allows you to grab "everything else" into a list. This is incredibly useful when parsing data streams or CSV rows.
# Grab the first and last, ignore the middle
scores = [10, 50, 60, 90, 100]
first, *middle, last = scores
print(first) # 10
print(middle) # [50, 60, 90] (Always a list!)
print(last) # 100
# Head/Tail separation (LISP style)
head, *tail = [1, 2, 3, 4]
print(head) # 1
print(tail) # [2, 3, 4]3. Deep Unpacking & Ignored Values
You can unpack nested structures and use _ (underscore) for values you don't care about.
user_record = ("Alice", 25, ("NY", "USA"))
# Unpack name and country only
name, _, (_, country) = user_record
print(name) # "Alice"
print(country) # "USA"
# _ is assigned 25, but we signal "ignore this"UNPACK_SEQUENCE) which are often faster than manual indexing (x = data[0]).Compound Assignment Operators
Compound assignment operators combine an operation with assignment, providing a concise way to update variables. These operators are not just syntactic sugar—they can be more efficient and express intent more clearly than their expanded forms.
# Arithmetic compound assignments
count = 10
count += 5 # count = count + 5 → 15
count -= 3 # count = count - 3 → 12
count *= 2 # count = count * 2 → 24
count /= 4 # count = count / 4 → 6.0 (float division)
count //= 2 # count = count // 2 → 3.0 (floor division)
count %= 2 # count = count % 2 → 1.0 (modulo)
count **= 3 # count = count ** 3 → 1.0 (exponentiation)
# String concatenation
message = "Hello"
message += " World" # "Hello World"
# List extension
numbers = [1, 2, 3]
numbers += [4, 5] # [1, 2, 3, 4, 5]Each compound operator modifies the variable in place when possible. For immutable types like integers and strings, a new object is created and the variable is rebound. For mutable types like lists, the object itself is modified, which can have surprising effects when multiple variables reference the same list.
Compound Assignment Operators Reference
| Operator | Example | Equivalent To | Description |
|---|---|---|---|
+= | x += 3 | x = x + 3 | Add and assign |
-= | x -= 3 | x = x - 3 | Subtract and assign |
*= | x *= 3 | x = x * 3 | Multiply and assign |
/= | x /= 3 | x = x / 3 | Divide and assign (float) |
//= | x //= 3 | x = x // 3 | Floor divide and assign |
%= | x %= 3 | x = x % 3 | Modulo and assign |
**= | x **= 3 | x = x ** 3 | Exponentiate and assign |
The Walrus Operator (:=)
Introduced in Python 3.8, the Assignment Expression operator (:=) allows you to assign a value to a variable inside an expression. It is a powerful tool for condensing loops and comprehensions.
Because this operator fundamentally changes how Python expressions work, we have dedicated an entire lesson to it. Learn how to use it to clean up while loops and optimize list comprehensions in the dedicated module.
Bitwise Compound Assignment
While arithmetic assignments are common, bitwise assignments (`|=`, `&=`) are secret weapons for managing state flags efficiently.
# Permissions System
READ = 1 # 001
WRITE = 2 # 010
EXEC = 4 # 100
user_perms = 0
# Grant permissions (Bitwise OR)
user_perms |= READ # 001
user_perms |= WRITE # 011
# Revoke permissions (Bitwise AND with Inverse)
user_perms &= ~WRITE # 001
# Check permission
has_read = (user_perms & READ) == READComparison Operators: Evaluating Relationships
Comparison operators evaluate the relationship between two values and return a boolean result (True or False). These operators are the foundation of conditional logic, enabling your programs to make decisions based on data. Python supports six comparison operators, each serving a specific purpose in logical expressions.
x = 10
y = 5
# Equality and inequality
print(x == y) # False - Equal to?
print(x != y) # True - Not equal to?
# Relational comparisons
print(x > y) # True - Greater than?
print(x < y) # False - Less than?
print(x >= 10) # True - Greater than or equal to?
print(x <= 5) # False - Less than or equal to?
# Chained comparisons (Pythonic!)
age = 25
print(18 <= age < 65) # True - age is between 18 and 65Python's comparison operators work intuitively with numbers, but they also support comparisons between strings (lexicographic order), lists (element-by-element), and even custom objects if you implement the appropriate magic methods. The ability to chain comparisons (a < b < c) is a Pythonic feature that makes range checks incredibly readable.
# String comparison (lexicographic order)
print("apple" < "banana") # True
print("Python" == "python") # False (case-sensitive)
# List comparison (element by element)
print([1, 2, 3] < [1, 2, 4]) # True
print([1, 2] < [1, 2, 0]) # False
# Tuple comparison
print((1, 2) < (1, 3)) # True
print((2, 1) < (1, 3)) # FalseThe Critical Difference: == vs is
One of the most important distinctions in Python is between the == operator and the is operator. While == checks for value equality (are these two things equal?), is checks for identity (are these the exact same object in memory?). Confusing these two is a common source of subtle bugs.
# Equality (==) - compares values
list1 = [1, 2, 3]
list2 = [1, 2, 3]
print(list1 == list2) # True - same values
print(list1 is list2) # False - different objects
# Identity (is) - compares object IDs
list3 = list1
print(list1 is list3) # True - exact same object
# The None check (the correct use of 'is')
value = None
if value is None: # ✅ Correct
print("Value is None")
if value == None: # ⌠Works but not recommended
print("This works but use 'is' instead")is when comparing to None,True, or False. These are singleton objects in Python, and identity checks are both faster and more semantically correct.Cloning Data: Shallow vs Deep Copy
Since assignment (=) only creates references, how do you actually copy an object? If you want to modify a list without affecting the original, you need to clone it. Python offers two ways to do this, and mixing them up is a common source of bugs.
1. Shallow Copy
Creates a new container, but populates it with references to the same child objects. This is the default behavior of slicing [:] and the list() constructor.
original = [[1, 2], [3, 4]]
shallow = original[:] # or list(original)
# Modifying the container is safe...
shallow.append([5, 6])
print(original) # [[1, 2], [3, 4]] (Unaffected)
# BUT modifying a child affects BOTH!
shallow[0][0] = 999
print(original) # [[999, 2], [3, 4]] (Affected!)
# Why? Because shallow[0] and original[0] point to the SAME list object.2. Deep Copy
Recursively copies everything. It creates a new container AND new child objects (and new children of children...). This is slower but truly independent.
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0][0] = 999
print(original) # [[1, 2], [3, 4]] (Clean!)The Scope of Assignment: global and nonlocal
By default, when you assign to a variable inside a function (x = 10), Python creates a local variable, even if a global variable with the same name exists. This protects your global state from accidental modification. But what if you want to modify the outer state?
1. The `global` Keyword
Use `global` to tell Python: "Don't create a new local variable; use the one defined at the top level."
counter = 0
def increment():
# counter += 1 # ⌠UnboundLocalError! Python sees assignment and assumes local.
global counter
counter += 1 # ✅ Modifies the global variable
print(counter)2. The `nonlocal` Keyword
Used in nested functions (closures). It tells Python to look in the nearest enclosing scope that isn't global.
def outer():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
# This creates a stateful closure!
fn = outer()
print(fn()) # 1
print(fn()) # 2global is often a sign of poor design. It makes code hard to test and debug because functions have "hidden inputs" (the global state). Prefer passing arguments and returning values.Real-World Applications
1. Input Validation Logic
One of the most common uses for comparison operators is validating user input. Whether you are building a CLI tool or a Web API, you must ensure data falls within safe boundaries.
def validate_age(age):
"""Validate age is within acceptable range"""
# Chained comparison simplifies logic
if not (0 <= age <= 150):
raise ValueError("Age must be between 0 and 150")
# Categorization logic
if age < 18:
return "minor"
elif age >= 65:
return "senior"
else:
return "adult"
def validate_password(password):
"""Check constraints using short-circuit logic"""
# Fail fast if length is wrong
if len(password) < 8:
return False
# Check composition using any()
# These return Booleans directly - no need for 'if'
has_digit = any(c.isdigit() for c in password)
has_upper = any(c.isupper() for c in password)
return has_digit and has_upper2. The Accumulator Pattern
Compound assignment operators (+=) are the backbone of "Accumulator" classes, used for statistics, scoring, or logging. They are cleaner than x = x + 1.
class WebsiteAnalytics:
def __init__(self):
self.page_views = 0
self.total_time = 0.0
def record_visit(self, duration):
# Update multiple metrics in place
self.page_views += 1
self.total_time += duration
def average_time(self):
# Compare vs 0 to avoid ZeroDivisionError
if self.page_views == 0:
return 0
return self.total_time / self.page_views3. Filtering & Comprehensions
Comparison operators define the logic for filtering datasets. When used inside List Comprehensions, they create a declarative way to query data.
products = [
{'{'}"name": "Laptop", "price": 999, "rating": 4.5{'}'},
{'{'}"name": "Mouse", "price": 25, "rating": 4.2{'}'},
{'{'}"name": "Keyboard", "price": 75, "rating": 4.7{'}'}
]
# "Select * from products where price between 20 and 100"
affordable = [
p for p in products
if 20 <= p["price"] <= 100
]
# "Select * from products order by price"
# Lambda uses the comparison value
sorted_products = sorted(products, key=lambda p: p["price"])Common Pitfalls & How to Avoid Them
⌠Mistake 1: Using = Instead of == in Conditions
Why it's wrong: Assignment (=) instead of comparison (==) in an if statement will cause a SyntaxErrorin Python (unlike C/JavaScript where it silently assigns).
# ⌠WRONG - SyntaxError
if x = 10:
print("This won't work")
# ✅ CORRECT
if x == 10:
print("This works!")How to avoid: Python actually protects you from this error! The syntax error is intentional. In other languages, this is a dangerous bug, but Python's design prevents it.
⌠Mistake 2: Comparing Floats with ==
Why it's wrong: Floating-point arithmetic is imprecise due to binary representation. Direct equality checks can fail unexpectably.
# ⌠WRONG - Unreliable
result = 0.1 + 0.2
if result == 0.3: # Might be False!
print("Equal")
print(0.1 + 0.2) # 0.30000000000000004
# ✅ CORRECT - Use tolerance
import math
def float_equal(a, b, tolerance=1e-9):
return abs(a - b) < tolerance
if float_equal(0.1 + 0.2, 0.3):
print("Equal within tolerance")
# Or use the math module
if math.isclose(0.1 + 0.2, 0.3):
print("Close enough!")⌠Mistake 3: Using is for Value Comparison
Why it's wrong: is checks object identity, not value equality. Due to Python's integer caching, it might work for small numbers but fail for larger ones.
# ⌠WRONG - Works sometimes, fails others
a = 1000
b = 1000
if a is b: # False (different objects)
print("Same")
# This confusingly works due to integer caching
x = 5
y = 5
if x is y: # True (cached integers -5 to 256)
print("Same")
# ✅ CORRECT - Use == for value comparison
if a == b:
print("Equal values")
# ✅ Only use 'is' for None, True, False
if value is None:
print("Value is None")⌠Mistake 4: Unexpected Behavior with += on Lists
Why it's wrong: += modifies lists in-place, affecting all references to that list object.
# ⌠UNEXPECTED BEHAVIOR
original = [1, 2, 3]
alias = original
alias += [4, 5]
print(original) # [1, 2, 3, 4, 5] - Modified!
print(alias) # [1, 2, 3, 4, 5]
# ✅ CORRECT - Create a new list if you don't want to modify
original = [1, 2, 3]
new_list = original + [4, 5] # Creates new list
print(original) # [1, 2, 3] - Unchanged
print(new_list) # [1, 2, 3, 4, 5]⌠Mistake 5: The Mutable Default Argument
Why it's wrong: Default arguments are evaluated only once when the function is defined, not every time it is called. If you use a mutable object (like a list) as a default, it persists across calls.
# ⌠WRONG
def add_item(item, data=[]):
data.append(item)
return data
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] 😱 - The list grew!
# ✅ CORRECT - Use None as a sentinel
def add_item_safe(item, data=None):
if data is None:
data = [] # Create a NEW list every time
data.append(item)
return dataBest Practices
✅ Do:
- Use compound assignment operators (
+=,-=) for clarity and efficiency - Always use
iswhen comparing toNone - Chain comparison operators for range checks:
0 <= x < 100 - Use
math.isclose()for floating-point comparisons - Be explicit with parentheses in complex comparisons
- Use
!=instead ofnot ==for clarity
⌠Don't:
- Don't use
== Trueor== False- use the value directly - Don't use
isfor value comparisons (exceptNone) - Don't compare floats with
==without tolerance - Don't chain too many assignments (
a = b = c = []with mutable objects) - Don't forget operator precedence in complex expressions
- Don't use assignment in conditions (Python prevents this anyway)
🎯 Key Takeaways
1. Assignment Binds Names to Objects
The = operator creates a reference from a variable name to an object in memory, not a copy. This matters especially with mutable objects.
2. Compound Operators Are Efficient
Operators like += and *= modify variables in-place when possible, making code more concise and often more performant.
3. == Checks Value, is Checks Identity
Use == to compare values (equality) and is to check if two variables reference the same object (identity). Always use is None.
4. Comparison Operators Return Booleans
All comparison operators (<, >, ==, etc.) return True or False, forming the foundation of conditional logic.
5. Chain Comparisons for Readability
Python allows chaining: a < b < c is more readable and efficient thana < b and b < c.
6. Beware of Floating-Point Comparison
Never use == for floats directly. Use math.isclose() with appropriate tolerance instead.