Python Tutorial / Understand Variable Scope, Aliasing and Mutable Objects
Scope determines where Python resolves a name, while mutability determines which changes are visible through names sharing an object. Distinguishing assignment from mutation explains many unexpected changes after function calls.
Separate local names from outer names
A name assigned inside a function is normally local unless explicitly declared otherwise. Reading an outer value differs from rebinding an outer name. Passing required values as arguments and returning results generally makes functions easier to test than relying on global state.
global refers to a module-level binding; nonlocal refers to a binding in an enclosing function. Before adding either declaration, consider whether an explicit data flow would make the operation clearer.
For the relevant APIs and platform requirements, see Understand Variable Scope, Aliasing and Mutable Objects documentation.
Save the complete code below as scope.py in a separate practice folder, using UTF-8. Run python scope.py in a terminal with your project virtual environment selected. Outside a virtual environment on macOS or Ubuntu, use python3 when that is your interpreter command.
def add_fee(items, fee):
items.append(fee)
original = [3500, 8000]
alias = original
copy = original.copy()
add_fee(alias, 500)
print(original)
print(copy)
nested = [[1], [2]]
shallow = nested.copy()
shallow[0].append(9)
print(nested)
Expected output:
[3500, 8000, 500] [3500, 8000] [[1, 9], [2]]
Compare assignment, shallow copy and deep copy
alias = original creates another name for the same list. copy() creates a new outer list but shares references to nested items. That shallow-copy behavior explains why changing shallow[0] also changes the inner list visible through nested.
copy.deepcopy can recursively copy structures, but it is not a universal way to duplicate files, live connections or intentionally shared state. Reconstruct only the data that should be independent, or choose immutable values when that makes the contract clearer.
Document whether a function changes its input
A caller should be able to tell whether a function mutates an input list. A function that preserves its input can instead return a new list. Test the original data as well as the return value; a correct result alone can miss an unintended side effect.
- Call add_fee(copy, 100) and confirm that original does not gain 100.
- Replace append inside the function with items = items + [fee] and observe that the external list stays unchanged.
- Restore the example and explain outer-list independence separately from nested-object sharing.
The default argument lesson shows another situation where an unexpectedly shared mutable object crosses function calls.




