Immutability means an object's own state cannot be modified after it is created. In Python, numbers, strings and tuples are immutable. For a tuple, the fixed part is its sequence of object references: you cannot replace a slot, insert another slot or remove one. An object reached through a slot may still have mutable state.
For a quant workflow, that distinction separates an archived price snapshot from a container that only looks frozen. A tuple of numbers can preserve the inputs a calculation used; a tuple containing a live list does not freeze that list.
A name can change without changing its object
Assignment to a name and assignment to a container item are different operations:
pair = (2, 3)
try:
pair[0] = 9
except TypeError as error:
print(type(error).__name__)
# TypeError
other_name = pair
pair = (9, 3)
print(other_name, pair)
# (2, 3) (9, 3)The failed item assignment tries to modify the tuple. The later
pair = (9, 3) rebinds pair to another tuple. other_name still refers to the original (2, 3). Immutability is a property of the object, not a promise that a variable name will always refer to it.An immutable shell can hold a changing list
Here the comma in
(items,) creates a one-element tuple:items = [2, 3]
record = (items,)
items.append(4)
print(record)
# ([2, 3, 4],)
try:
hash(record)
except TypeError as error:
print(type(error).__name__)
# TypeErrorThe tuple still has one slot pointing to the same list. That list now holds three integers. Anyone holding another reference to it sees the mutation too. Deep immutability would require the reachable contents to be immutable as well; wrapping a list in a tuple does not achieve this.
This also explains why “immutable” is insufficient as a test for dictionary-key eligibility. Python dictionaries use a hash table and require hashable keys. A tuple is hashable only when all its elements are hashable. The list in
record is unhashable, so hashing the outer tuple raises TypeError.Original object-reference diagram. Prices are fictional USD per share. Left: rebinding preserves the old tuple. Right: fixed tuple slots do not freeze the list they reference.
Keep the snapshot; create the update
An immutable representation is useful when a pricing run needs to retain its inputs while new observations arrive:
latest = (100, 101) # fictional prices in USD per share
archived = latest
latest = latest + (102,)
print(archived)
# (100, 101)
print(latest)
# (100, 101, 102)Tuple concatenation produces another tuple here. Both elements of the archived tuple are immutable integers, so the update cannot rewrite those archived price values. A similar design can retain a pricing configuration as immutable scalar fields and create a new configuration for each scenario. It fits functional programming, where transformations return results rather than changing shared inputs.
Immutability supports research reproducibility, but it does not by itself provide a durable audit trail or replay an entire run. Retain the snapshot with its timestamp, units, data provenance, code and environment versions. Shared mutable dependencies and coordination between concurrent tasks still need their own design.
For a guided introduction to collection choice and shared references, read Collections, available with a free signed-in account.
Sources
- Python 3.14 data model: objects, values and types — object mutability and the fixed references inside immutable containers.
- Python 3.14 language reference: assignment statements — rebinding names versus assigning to items.
- Python 3.14 built-in types: immutable sequence types — hashing immutable sequences with unhashable elements.
- Python 3.14 glossary: hashable — dictionary-key requirements.