Let’s take a closer look at how arguments are passed to functions. In many programming languages, there are two ways to pass arguments—pass-by-value and pass-by-reference (sometimes called call-by-value and call-by-reference, respectively):
With pass-by-value, the called function receives a copy of the argument’s value and works exclusively with that copy. Changes to the function’s copy do not affect the original variable’s value in the caller.
With pass-by-reference, the called function can access the argument’s value in the caller directly and modify the value if it’s mutable.
Python arguments are always passed by reference. Some people call this pass-by-object-reference, because “everything in Python is an object.”3 When a function call provides an argument, Python copies the argument object’s reference—not the object itself—into the corresponding parameter. This is important for performance. Functions often manipulate large objects—frequently copying them would consume large amounts of computer memory and significantly slow program performance.
You interact with an object via a reference, which behind the scenes is that object’s address (or location) in the computer’s memory—sometimes called a “pointer” in other languages. After an assignment like
x = 7
the variable x
does not actually contain the value 7
. Rather, it contains a reference to an object containing 7
(and some other data we’ll discuss in later chapters) stored elsewhere in memory. You might say that x
“points to” (that is, references) the object containing 7
, as in the diagram below:
id
and Object IdentitiesLet’s consider how we pass arguments to functions. First, let’s create the integer variable x
mentioned above—shortly we’ll use x
as a function argument:
In [1]: x = 7
Now x
refers to (or “points to”) the integer object containing 7
. No two separate objects can reside at the same address in memory, so every object in memory has a unique address. Though we can’t see an object’s address, we can use the built-in id
function to obtain a unique int
value which identifies only that object while it remains in memory (you’ll likely get a different value when you run this on your computer):
In [2]: id(x)
Out[2]: 4350477840
The integer result of calling id
is known as the object’s identity.4 No two objects in memory can have the same identity. We’ll use object identities to demonstrate that objects are passed by reference.
Let’s define a cube
function that displays its parameter’s identity, then returns the parameter’s value cubed:
In [3]: def cube(number):
...: print('id(number):', id(number))
...: return number ** 3
...:
Next, let’s call cube
with the argument x
, which refers to the integer object containing 7
:
In [4]: cube(x)
id(number): 4350477840
Out[4]: 343
The identity displayed for cube
’s parameter number
—4350477840
—is the same as that displayed for x
previously. Since every object has a unique identity, both the argument x
and the parameter number
refer to the same object while cube
executes. So when function cube
uses its parameter number
in its calculation, it gets the value of number
from the original object in the caller.
is
OperatorYou also can prove that the argument and the parameter refer to the same object with Python’s is
operator, which returns True
if its two operands have the same identity:
In [5]: def cube(number):
...: print('number is x:', number is x) # x is a global variable
...: return number ** 3
...:
In [6]: cube(x)
number is x: True
Out[6]: 343
When a function receives as an argument a reference to an
immutable (unmodifiable) object—such as an int
, float
, string
or tuple
—even though you have direct access to the original object in the caller, you cannot modify the original immutable object’s value. To prove this, first let’s have cube
display id(number)
before and after assigning a new object to the parameter number
via an augmented assignment:
In [7]: def cube(number):
...: print('id(number) before modifying number:', id(number))
...: number **= 3
...: print('id(number) after modifying number:', id(number))
...: return number
...:
In [8]: cube(x)
id(number) before modifying number: 4350477840
id(number) after modifying number: 4396653744
Out[8]: 343
When we call cube(x)
, the first print
statement shows that id(number)
initially is the same as id(x)
in snippet [2]
. Numeric values are immutable, so the statement
number **= 3
actually creates a new object containing the cubed value, then assigns that object’s reference to parameter number
. Recall that if there are no more references to the original object, it will be garbage collected. Function cube
’s second print
statement shows the new object’s identity. Object identities must be unique, so number
must refer to a different object. To show that x
was not modified, we display its value and identity again:
In [9]: print(f'x = {x}; id(x) = {id(x)}')
x = 7; id(x) = 4350477840
In the next chapter, we’ll show that when a reference to a mutable object like a list is passed to a function, the function can modify the original object in the caller.
(Fill-In) The built-in function returns an object’s unique identifier.
Answer: id
.
(True/False) Attempts to modify mutable objects create new objects.
Answer: False. This is true for immutable objects.
(IPython Session) Create a variable width
with the value 15.5
, then show that modifying the variable creates a new object. Display width
’s identity and value before and after modifying its value.
Answer:
In [1]: width = 15.5
In [2]: print('id:', id(width), ' value:', width)
id: 4397553776 value: 15.5
In [3]: width = width * 3
In [4]: print('id:', id(width), ' value:', width)
id: 4397554208 value: 46.5
18.119.111.9