Subroutine interfaces, scope and shared objects
By UK A Level Computer Science · independent study · myAudience
![Names a and b share [2,5]; c has a separate outer copy. In the nested example a[0] and c[0] reference the same inner [2], while c[1] references [9].](/assets/lesson-computing-advanced-alias-v2.webp)
Original textbook concept plate generated with OpenAI imagegen; labels and states checked against the lesson model. · Generated original teaching artwork; pending operator rights/provenance review before publication.
Subroutine interfaces, scope and shared objects
A subroutine packages a named operation behind an interface. A parameter is a name in its definition; an argument is the value supplied by a particular call. A function returns a result to its caller, while a procedure is used chiefly for its effect; Python functions always return a value, implicitly None if no explicit return executes. The interface should specify the accepted data, the result and any mutation or failure. A short function with an undocumented side effect can be harder to use safely than a longer function with a clear contract.
Local names belong to a particular call's scope. Rebinding a local parameter does not automatically rebind the caller's name. However, local scope does not make the referenced object private. In Python, argument passing binds a local name to the same object supplied by the caller. If that object is mutable, a method such as append changes the shared object. Describing Python as “always pass by reference” is imprecise because assignment to the parameter name does not change the caller's binding. Reason in terms of names, object identity and operations instead.
Suppose a=[2,5] and b=a. Both names refer to one list. Calling a function that appends 9 through its parameter changes the list seen through both a and b. In contrast, append_copy first creates a separate outer list and mutates that copy. Its postcondition is result=original contents followed by the item, with the original outer list unchanged. Copying takes O(n) time and O(n) additional storage, so purity has a cost here. That cost can be worthwhile when it prevents distant components from observing unexpected changes.
A shallow copy duplicates the outer container while preserving references to nested objects. If a=[[2],[5]], append_copy(a,[9]) protects the outer list only; changing result[0][0] still changes a[0][0]. A deep copy or immutable representation is needed for a stronger isolation contract. Do not promise “the data cannot change” when the implementation only copies one layer. This is a useful example of data abstraction: callers depend on a stated behavioural contract, and its exact boundaries determine what substitutions are safe.
Global variables are names available in wider module scope. They can hold constants or shared state, but uncontrolled global mutation couples calls and makes order matter. Our calculation accepts all necessary data explicitly and returns its result. That is a recommended design choice for this example, not a requirement that every system contain no shared state. A hierarchy chart describes which subroutines call others; it does not describe the temporal sequence of every call or the ownership of every object. Use the right model for each question.
Reading the diagram: The left panel has two names a and b pointing to one list [2,5], and c pointing to a separate list [2,5,9]. The right panel starts a separate nested example: a has one outer slot, while c has two. Both outer slot-zero arrows reach the same inner [2]; only c slot one reaches [9]. Appending 7 through c[0] therefore changes the object also reached through a[0]. Arrows mean references and identity, not actual memory addresses or copied numeric values.
Runnable original Python 3, independently executed on Python the related topic. Read the stated input contract before running. These examples use fictional data and are teaching models, not production services.
```python
def append_copy(values, item):
"""Return a new list, leaving the original list object untouched."""
result = values.copy()
result.append(item)
return result
```
Worked example: Let a=[2,5], b=a and c=append_copy(a,9). Compare identity, contents and a later c.append(12).
a and b are aliases for the same list, whose contents remain [2,5].
c is a new outer list with [2,5,9], so c is a is false while b is a is true.
Appending 12 to c changes c to [2,5,9,12], leaving a and b unchanged.
Answer: The function preserves the original outer list, but its copy is shallow.
Check your understanding: 1. Distinguish parameter and argument. 2. For a=[[2]], show an operation on c=append_copy(a,[9]) that changes a. 3. Redesign an interface for summing readings so that an input error cannot be confused with the legitimate sum zero.
Answer: 1. values is a parameter; the list object passed at a call is an argument. 2. c[0].append(7) changes a to [[2,7]] because the inner list is shared. 3. Return a defined success/error tagged result or raise a documented exception for invalid inputs; reserve zero for successful calculations whose sum is zero. Parsing can be separated from the pure summation function.
Why: Local names do not guarantee private objects. A shallow copy does not recursively copy a structure. Returning None is a value, not proof of successful mutation. Interface reasoning must include identity, effects and meaningful failure behaviour.