# Python Code Tracing Practice for Developers

> Trace Python code by following object references, loop state, function calls, and mutation deliberately instead of assuming assignment creates an independent copy.

- Canonical HTML: [https://unrust.dev/languages/python/code-tracing](https://unrust.dev/languages/python/code-tracing)
- Markdown representation: [https://unrust.dev/languages/python/code-tracing/index.md](https://unrust.dev/languages/python/code-tracing/index.md)

Published: 2026-07-14

## Direct answer

Trace Python by choosing a concrete input and recording the variables, object identities, branch decisions, and mutations that affect the result. Pay particular attention to aliases of lists and dictionaries, in-place methods that return None, and function calls that change an object a caller still references. The trace is a hypothesis you can check with a debugger or a focused assertion, not a substitute for executing uncertain behavior.

## Follow aliases through mutable collections

Python assignment binds another name to the same object until you deliberately create a copy. Mark aliases in a state table and note whether append, sort, update, or a helper call changes the shared value in place. This prevents a local-looking edit from becoming an unexpected change in a caller or sibling branch.

## Write loop invariants in plain language

For a loop, state what the accumulator, index, and collection represent before and after each iteration. This clarifies whether a branch handles the first item, an empty iterable, or the final update correctly, and it gives you a specific assertion to run when the actual result surprises you.

## Original example

### Trace a list alias before sorting it in place

```python
ranked = scores
ranked.sort()

return scores[0]
```

This original Python example requires an identity-aware trace: ranked and scores refer to the same list, and sort mutates it while returning None. The useful review question is whether the caller expects scores to keep its original order. If not, a copied or non-mutating approach may be appropriate, but that choice should follow the surrounding ownership contract.

## Checklist

- Which Python names refer to the same mutable object?
- Does this method mutate in place or return a new value?
- What invariant should hold after each loop iteration or helper call?

## Practice loop

1. Draw object-reference arrows for each Python list or dictionary passed into a small function.
2. Record the accumulator and collection state after each loop iteration that can affect the result.
3. Check the trace with a focused assertion or debugger at the first unexpected mutation.

## Limits of this page

Manual tracing is less complete when generators, threads, async tasks, C extensions, or external I/O shape the behavior. A short trace is only a constrained snapshot and cannot determine whether unfamiliarity, time pressure, or a bad day caused a mistaken prediction.

## Source context

Editorially reviewed for Unrust using public language documentation, common code-review practice, and small original examples written for this resource system.

## Related resources

- [How to Trace State in Unfamiliar Code](https://unrust.dev/use-cases/trace-state-in-unfamiliar-code/index.md): Trace state in an unfamiliar function by following only the values, references, branches, and side effects that can change the behavior you are investigating.
- [How to Understand an AI-Generated Pull Request](https://unrust.dev/use-cases/understand-an-ai-generated-pull-request/index.md): Understand an AI-generated pull request by mapping the request to the diff, tracing its data flow, and isolating every assumption before approval.

Next step: [Take the free diagnostic](https://unrust.dev/diagnostic/index.md)
