Object References and Argument Parsing
August 30, 2022
Overview
Programming languages have various implementations for how objects are passed as arguments to functions. Python uses a pass-by-assignment model, which differs from the traditional pass-by-reference and pass-by-value models.
Objects
In Python, everything is an object and every object has an identity, a type and a value.
- an object’s identity refers to the object's address in memory
- an object’s type determines the possible operations that the object supports and the possible values for objects of that type
- an object's value refers to the content's of the object
An object's identity and type can't change, however, the object's value can change. This is referred to as mutability and is determined by the object's type.
Arguments
Arguments allow you to pass objects into a function so that the functions's behavior can vary from one invocation to the next.
Argument Parsing Precedent
When a function is called, a list of unfilled slots is created for the arguments. Positional arguments are placed into the first slots. Subsequently, keyword arguments are placed into the slot at the index where either the keyword name matches the parameter name or at the end. If the slot is already filled, an error is thrown. Slots that are still unfilled are filled with the corresponding default value from the function signature. If there are unfilled slots for which no default value exists, an error is thrown.
Pass-By-Reference
With pass-by-reference, a reference to the object in the calling environment is passed as an argument to the function. The parameter is an alias for the argument, referencing the same memory address as the object passed in. If the argument is modified within the function, the corresponding object in the calling environment is modified as well.
Usage
- useful in high-performance applications (copying a reference to an object is more efficient than copying the object itself)
- useful if you want to modify the object in the calling environment
Python is Not Pass-By-Reference
Consider this example:
def f(a):
a = ["x", "y", "z"]
a = ["a", "b", "c"]
f(a)
print(a)
['a', 'b', 'c'] # inconsistent with pass-by-reference
Pass-By-Value
With pass-by-value, a copy of the object in the calling environment is passed as an argument to the function. The parameter is independent of the argument, referencing a different memory address as the object passed in. If the argument is modified within the function, the corresponding object in the calling environment is not modified.
Usage
- useful in multithreaded applications (copying the object itself ensures an object's state is not altered by other threads)
- useful if you don't want to modify the object in the calling environment
Python is Not Pass-By-Value
Consider this example:
def f(a):
a.append("d")
a = ["a", "b", "c"]
f(a)
print(a)
["a", "b", "c", "d"] # inconsistent with pass-by-value
Pass-By-Assignment
As shown above, Python is not pass-by-reference or pass-by-value. Instead, Python uses a pass-by-assignment model.
When making assignments in Python, a variable name is assigned to reference an object. The object either exists at a location in memory or is created on the fly. The variable name itself does not create this location in memory, it simply refers to the object. Therefore, the reference exists between the variable name and the object, not between the variable name and the object's location in memory.
When a function is called, each parameter name is assigned to reference the object of the argument that is passed in (if any). There is no coupling between the variable name in the calling environment and the paramater name in the function; they are unique references to the same object. Assignments made to new objects within a function are scoped locally to the function. However, modifications made to existing objects within a function are scoped globally to the calling environment.
Breaking Down the Pass-By-Reference Example
From the pass-by-reference example above: if a parameter name is assigned to a new object within the function, the parameter reference is simply reassigned to the new object. When the function returns, the new object is destroyed and the variable name in the calling environment continues to reference the original object.
def f(a):
a = ["x", "y", "z"]
a = ["a", "b", "c"]
f(a)
print(a)
Before function execution (in the calling environment):
- the variable named
a
is assigned to reference the object["a", "b", "c"]
(object 1)- the object is created on the fly at a location in memory
- there is one reference to object 1: the variable in the calling environment
- the object (that
a
refers to) is passed as an argument to the functionf
During function execution (within the function):
- the parameter named
a
is assigned to reference the object["a", "b", "c"]
- there are two references to object 1: the variable in the calling environment and the parameter in the function
- the parameter named
a
is assigned to reference the object["x", "y", "z"]
(object 2)- this object is created on the fly at a location in memory
- there is one reference to object 1: the variable in the calling environment
- there is one reference to object 2, the parameter in the function
- object 2 is scoped locally to the function and is destroyed when the function returns
After function execution (in the calling environment):
- the variable named
a
still references object 1, which has a value of["a", "b", "c"]
Breaking Down the Pass-By-Value Example
From the pass-by-value example above: if a parameter name references a mutable object and that mutable object is modified, the corresponding object in the calling environment will also be modified.
def f(a):
a.append("d")
a = ["a", "b", "c"]
f(a)
print(a)
Before function execution (in the calling environment):
- the variable named
a
is assigned to reference the object["a", "b", "c"]
(object 1)- the object is created on the fly at a location in memory
- there is one reference to object 1: the variable in the calling environment
- the object (that
a
refers to) is passed as an argument to the functionf
During function execution (within the function):
- the parameter named
a
is assigned to reference the object["a", "b", "c"]
- there are two references to object 1: the variable in the calling environment and the parameter in the function
- object 1 is modified (it is a mutable object)
- there are still two references to object 1: the variable in the calling environment and the parameter in the function
- object 1 is scoped globally to the function and is not destroyed when the function returns
After function execution (in the calling environment):
- the variable named
a
still references object 1, which now has a value of["a", "b", "c", "d]