ML Foundations

Avoid shared rows when initializing Python batches

Recognize repeated references in a nested list and construct an independent list for each row.

Evidence and testing

Review record: Tensor Almanac automated evidence gate (no independent human review). Example versions, recorded outputs and limitations appear below.

A repeated row is one shared list

A nested list can hold small batches of mutable feature values. The expression a two-element row repeated three times creates an outer list with three references to the same inner list. It does not make three independent inner lists. Changing one element through the first row is therefore visible through the other two references. [1, 2]

This is a reference-sharing issue, not a numerical operation. It can surprise code that updates one example at a time and assumes each row has its own storage. Comparing row identities makes the relationship visible.

References: [1], [2]

Construct each row independently

Run python examples/python_shared_batch_rows.py. The first fixture uses repetition, changes the second value in the first row and prints all three rows. It also checks that the first two rows are the same object. The second fixture evaluates a fresh row expression in a list comprehension for each iteration. [1]

For the repeated fixture all three rows show zero and seven. In the comprehension fixture only the first row changes; the other two remain two zeros. Assertions verify the outputs, and the identity checks explain why they differ. This test uses plain Python lists and requires no machine-learning framework.

Use a comprehension when each row needs separate mutable storage. If the row contains nested mutable objects of its own, those inner objects also need an appropriate independent construction; changing only the outer list is not enough.

The additional fixture shows the deeper case: constructing separate rows still leaves two cells in each row sharing one mutable list when the cell expression is repeated. Constructing each cell with its own comprehension makes the append visible only in the selected cell. The assertions check both patterns.

Executed with Python 3.12.14; stdout and checksum are saved in private evidence.

"""Show why repeated nested lists share one mutable row."""
shared = [[0, 0]] * 3
shared[0][1] = 7
print('shared:', shared)
print('same object:', shared[0] is shared[1])
independent = [[0, 0] for _ in range(3)]
independent[0][1] = 7
print('independent:', independent)
print('different objects:', independent[0] is not independent[1])
assert shared == [[0, 7], [0, 7], [0, 7]]
assert independent == [[0, 7], [0, 0], [0, 0]]

# Construct nested mutable cells independently as well as their rows.
shallow = [[[]] * 2 for _ in range(2)]
shallow[0][0].append(9)
print('shared cells:', shallow)
separate = [[[] for _ in range(2)] for _ in range(2)]
separate[0][0].append(9)
print('independent cells:', separate)
assert shallow == [[[9], [9]], [[], []]]
assert separate == [[[9], []], [[], []]]

Recorded output

shared: [[0, 7], [0, 7], [0, 7]]
same object: True
independent: [[0, 7], [0, 0], [0, 0]]
different objects: True
shared cells: [[[9], [9]], [[], []]]
independent cells: [[[9], []], [[], []]]

References: [1], [2]

Check the boundary before updating data

If rows unexpectedly change together, inspect object identity before and after construction and trace which container receives the mutation. Equality checks compare contents and do not by themselves show whether the two rows are the same object.

This example isolates a three-row fixture. It does not test NumPy view semantics, copies made by framework data loaders, multiprocessing or memory consumption. Those require separate checks for the actual data structure and execution path.

References: [1], [2]

Sources

  1. Python FAQ: multidimensional lists
  2. Python sequence repetition