Skip to content

Immutability

An object-level restriction on change: why fixed tuple slots preserve scalar snapshots but do not freeze nested lists or guarantee hashability.

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__) # TypeError
The 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.
An archived name points to the price tuple (100, 101), while latest points to a different tuple (100, 101, 102). A one-slot tuple can keep pointing to the same list as that list changes from [2, 3] to [2, 3, 4].
An archived name points to the price tuple (100, 101), while latest points to a different tuple (100, 101, 102). A one-slot tuple can keep pointing to the same list as that list changes from [2, 3] to [2, 3, 4].
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

Last updated
  • python
  • immutability
  • mutable state
  • object references
  • reproducibility
← Back to the glossaryBack to top ↑