Python Mutable vs Immutable Objects: Memory Addresses, References & In-Place Mutability

In Python, every entity is an object categorized as either mutable (modifiable in-place) or immutable (read-only after creation).

Memory Allocation & Identity: When modifying a mutable object like a list, dictionary, or set, changes occur directly in memory without altering its identity (memory address). Conversely, modifying or reassigning an immutable object such as an int, string, tuple, bytes, or frozenset() allocates a new object at a different memory address. To test if objects are hashable, see hash(); to inspect object classifications, see type() and membership testing with in and not in.

Infographic: Memory Model (Mutable vs. Immutable)
MUTABLE (list, dict, set) var: a Address: 0x10A (id unchanged) [10, 20] → [10, 20, 99] ✔ In-Place Mutation: address remains identical IMMUTABLE (int, str, tuple) var: s 0x20A ["hello"] (Abandoned) 0x30B ["world"] (New Address) ✘ Cannot Modify: rebinds to new object

Figure 1: Memory pointer model: mutable objects update in-place at the same address; immutable objects allocate new memory.

1. Understanding Objects: Identity, Type, and Value 🔝

Every entity in Python has three core attributes determining its behavior:

  • Identity: The memory address where the object is allocated, inspected via id(). Unique during object lifetime.
  • Type: The object's class category (e.g. int, str, list), inspected with type().
  • Value: The data stored inside the object. Mutable objects allow in-place value changes; immutable objects keep their value fixed.
# 1. Object identity, type, and value
name = "plus2net"
nums = [10, 20]
print("name id:", id(name), "| type:", type(name))
print("nums id:", id(nums), "| type:", type(nums))

Terminal Output:

name id: 140301582741432 | type: <class 'str'>
nums id: 140301581527624 | type: <class 'list'>

2. Comprehensive Classification: Mutable vs. Immutable Types 🔝

Understanding which built-in data types are mutable or immutable helps avoid unintended mutations:

Category Python Data Types In-Place Modification? Hashable as Dict Key / Set Element?
Immutable int, float, bool, complex No (reassigns memory) Yes (implements __hash__)
Immutable str, bytes No (read-only sequence) Yes
Immutable tuple, frozenset No (fixed container) Yes (if children are hashable)
Mutable list, bytearray Yes (appends, mutations) No (raises TypeError: unhashable)
Mutable dict, set Yes (insertions, deletions) No

Attempting item assignment on an immutable string raises a TypeError, while modifying a list succeeds:

# 2. Immutable TypeError vs. Mutable mutation
site = "plus2net.com"
try:
    site[4] = "5"
except TypeError as err:
    print("Immutable str error:", err)

lst = [1, 2, 3]
lst[1] = 8
print("Mutable list update:", lst)

Terminal Output:

Immutable str error: 'str' object does not support item assignment
Mutable list update: [1, 8, 3]

3. Memory Address Tracking: Comparing list vs. str with id() 🔝

Tracking addresses with id() reveals how Python treats modifications. Changing an item in a list keeps the same address, while reassigning a string allocates a new object:

# 3. Comparing list vs. string address changes
my_list = [1, 2, 3]
str1 = "welcome"
print("Initial list id:", id(my_list), "| str id:", id(str1))

my_list[1] = 7      # In-place update: id stays identical
str1 = "Python"     # Reassignment: allocates a new object
print("Updated list id:", id(my_list), "(Same)")
print("Updated str id: ", id(str1), "(Changed)")

Terminal Output:

Initial list id: 140301581527624 | str id: 140301582741432
Updated list id: 140301581527624 (Same)
Updated str id:  140302155689016 (Changed)

4. In-Place Augmented Assignment (+=): List vs. Tuple 🔝

The += operator behaves differently depending on whether the operand is mutable or immutable:

  • Mutable (list): Invokes __iadd__() to extend the container in-place at the exact same memory address.
  • Immutable (tuple): Constructs a new combined tuple and rebinds the variable to the new address.
# 4. In-place += on list vs. tuple
l = [5, 7]
orig_l_id = id(l)
l += [9]
print("List id unchanged:", id(l) == orig_l_id)

t = (1, 2)
orig_t_id = id(t)
t += (3,)
print("Tuple id changed:  ", id(t) != orig_t_id)

Terminal Output:

List id unchanged: True
Tuple id changed:   True

5. The Nested Immutability Trap: Tuples Containing Mutable Lists 🔝

A tuple holds immutable references to its items. However, if one of those items is a mutable list, the list's contents can still be mutated in-place:

# 5. Nested immutability trap
record = ("Alex", [90, 85])
print("Initial tuple:", record)
record[1].append(100)  # Modifies inner list in-place
print("Modified tuple:", record)

Terminal Output:

Initial tuple: ('Alex', [90, 85])
Modified tuple: ('Alex', [90, 85, 100])

6. Function Arguments & The Mutable Default Parameter Trap 🔝

Python evaluates default parameter values once at function definition time. Using a mutable default parameter (like target=[]) shares the same container across subsequent calls. Use None as a sentinel value instead:

# 6. Mutable default parameter trap and safe fix
def bad_add(item, target=[]):
    target.append(item)
    return target

print("bad_add 1:", bad_add("A"))
print("bad_add 2:", bad_add("B"))  # Accumulates!

def safe_add(item, target=None):
    if target is None:
        target = []
    target.append(item)
    return target

print("safe_add 1:", safe_add("A"))
print("safe_add 2:", safe_add("B"))

Terminal Output:

bad_add 1: ['A']
bad_add 2: ['A', 'B']
safe_add 1: ['A']
safe_add 2: ['B']

7. Hashability & Dictionary Keys 🔝

Dictionaries and sets require keys and elements to be hashable:

  • Only immutable types (strings, numbers, tuples, frozenset) can be hashed with hash().
  • Mutable objects (lists, dictionaries, sets) do not implement __hash__ because mutations would invalidate hash table lookups.
# 7. Hashability & Dictionary Keys
lookup = {(10, 20): "Alpha", frozenset([1, 2]): "Beta"}
print("Immutable keys lookup:", lookup[(10, 20)])

try:
    bad = {[1, 2]: "Gamma"}
except TypeError as err:
    print("Mutable key error:", err)

Terminal Output:

Immutable keys lookup: Alpha
Mutable key error: unhashable type: 'list'

8. Production Recipes: Read-Only Dict Protection 🔝

Protecting runtime configuration dictionaries using MappingProxyType to enforce immutability:

# 8. Recipe: Read-only dict protection
from types import MappingProxyType

config = {"host": "localhost", "port": 8080}
frozen_cfg = MappingProxyType(config)

try:
    frozen_cfg["port"] = 9000
except TypeError as err:
    print("Protection verified:", err)

Terminal Output:

Protection verified: 'mappingproxy' object does not support item assignment

9. Interactive Practice & Unit Assertion Suite 🔝

Practice testing mutability rules, tracking memory addresses with id(), and avoiding argument traps in our companion Jupyter Notebook:

Interactive Companion Jupyter Notebook

Explore interactive code cells, benchmark memory addresses with id(), test tuple mutability edges, and run self-validating assertions.

View on GitHub Run in Google Colab

Self-Verifying Assertion Suite

Run these unit checks directly in Python to verify mutability, identity stability, and hashability rules:

# 9. Unit assertions verifying mutability rules
a = [10, 20]
a_id = id(a)
a.append(30)
assert id(a) == a_id

s = "abc"
s_id = id(s)
s += "d"
assert id(s) != s_id

t = (1, [2])
t[1].append(3)
assert t == (1, [2, 3])

assert hash((1, 2)) is not None
print("All mutable vs. immutable unit assertions passed!")

Terminal Output:

All mutable vs. immutable unit assertions passed!

10. Frequently Asked Questions (FAQ) 🔝

Is a tuple always immutable?

The tuple container itself is immutable (its references cannot be altered). However, if an item inside the tuple is a mutable list, that inner list can mutate in-place.

Why can't lists be dictionary keys?

Lists are mutable and unhashable. If a list were mutated after being stored as a key, its hash bucket position would become invalid, corrupting lookup operations.

How does Python pass function arguments?

Python uses pass-by-object-reference. Mutating a passed mutable object modifies the caller's data; reassigning the parameter rebinds only the local variable name.

How can I create an immutable set?

Use the built-in frozenset() constructor. Unlike standard sets, a frozenset is immutable, read-only, and fully hashable.

Further Reading & Related Tutorials 🔝




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer