Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Pass by Reference in Python: Best Practices
After gaining some familiarity with Python, you may notice cases in which your functions donβt modify arguments in place as you might expect, especially if youβre familiar with other programming languages. Some languages handle function arguments as references to existing variables, which is known as pass by reference. Other languages handle them as independent values, an approach known as pass by value.
If youβre an intermediate Python programmer who wishes to understand Pythonβs peculiar way of handling function arguments, then this tutorial is for you. Youβll implement real use cases of pass-by-reference constructs in Python and learn several best practices to avoid pitfalls with your function arguments.
In this tutorial, youβll learn:
- What it means to pass by reference and why youβd want to do so
- How passing by reference differs from both passing by value and Pythonβs unique approach
- How function arguments behave in Python
- How you can use certain mutable types to pass by reference in Python
- What the best practices are for replicating pass by reference in Python
Free Bonus: 5 Thoughts On Python Mastery, a free course for Python developers that shows you the roadmap and the mindset youβll need to take your Python skills to the next level.
Defining Pass by Reference
Before you dive into the technical details of passing by reference, itβs helpful to take a closer look at the term itself by breaking it down into components:
- Pass means to provide an argument to a function.
- By reference means that the argument youβre passing to the function is a reference to a variable that already exists in memory rather than an independent copy of that variable.
Since youβre giving the function a reference to an existing variable, all operations performed on this reference will directly affect the variable to which it refers. Letβs look at some examples of how this works in practice.
Below, youβll see how to pass variables by reference in C#. Note the use of the ref keyword in the highlighted lines:
using System;
// Source:
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/passing-parameters
class Program
{
static void Main(string[] args)
{
int arg;
// Passing by reference.
// The value of arg in Main is changed.
arg = 4;
squareRef(ref arg);
Console.WriteLine(arg);
// Output: 16
}
static void squareRef(ref int refParameter)
{
refParameter *= refParameter;
}
}
As you can see, the refParameter of squareRef() must be declared with the ref keyword, and you must also use the keyword when calling the function. Then the argument will be passed in by reference and can be modified in place.
Python has no ref keyword or anything equivalent to it. If you attempt to replicate the above example as closely as possible in Python, then youβll see different results:
>>> def main():
... arg = 4
... square(arg)
... print(arg)
...
>>> def square(n):
... n *= n
...
>>> main()
4
In this case, the arg variable is not altered in place. It seems that Python treats your supplied argument as a standalone value rather than a reference to an existing variable. Does this mean Python passes arguments by value rather than by reference?
Not quite. Python passes arguments neither by reference nor by value, but by assignment. Below, youβll quickly explore the details of passing by value and passing by reference before looking more closely at Pythonβs approach. After that, youβll walk through some best practices for achieving the equivalent of passing by reference in Python.
Contrasting Pass by Reference and Pass by Value
When you pass function arguments by reference, those arguments are only references to existing values. In contrast, when you pass arguments by value, those arguments become independent copies of the original values.
Letβs revisit the C# example, this time without using the ref keyword. This will cause the program to use the default behavior of passing by value:
using System;
// Source:
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/passing-parameters
class Program
{
static void Main(string[] args)
{
int arg;
// Passing by value.
// The value of arg in Main is not changed.
arg = 4;
squareVal(arg);
Console.WriteLine(arg);
// Output: 4
}
static void squareVal(int valParameter)
{
valParameter *= valParameter;
}
}
Here, you can see that squareVal() doesnβt modify the original variable. Rather, valParameter is an independent copy of the original variable arg. While that matches the behavior you would see in Python, remember that Python doesnβt exactly pass by value. Letβs prove it.
Pythonβs built-in id() returns an integer representing the memory address of the desired object. Using id(), you can verify the following assertions:
- Function arguments initially refer to the same address as their original variables.
- Reassigning the argument within the function gives it a new address while the original variable remains unmodified.
In the below example, note that the address of x initially matches that of n but changes after reassignment, while the address of n never changes:
>>> def main():
... n = 9001
... print(f"Initial address of n: {id(n)}")
... increment(n)
... print(f" Final address of n: {id(n)}")
...
>>> def increment(x):
... print(f"Initial address of x: {id(x)}")
... x += 1
... print(f" Final address of x: {id(x)}")
...
>>> main()
Initial address of n: 140562586057840
Initial address of x: 140562586057840
Final address of x: 140562586057968
Final address of n: 140562586057840
The fact that the initial addresses of n and x are the same when you invoke increment() proves that the x argument is not being passed by value. Otherwise, n and x would have distinct memory addresses.
Before you learn the details of how Python handles arguments, letβs take a look at some practical use cases of passing by reference.
Using Pass by Reference Constructs
Passing variables by reference is one of several strategies you can use to implement certain programming patterns. While itβs seldom necessary, passing by reference can be a useful tool.
In this section, youβll look at three of the most common patterns for which passing by reference is a practical approach. Youβll then see how you can implement each of these patterns with Python.
Avoiding Duplicate Objects
As youβve seen, passing a variable by value will cause a copy of that value to be created and stored in memory. In languages that default to passing by value, you may find performance benefits from passing the variable by reference instead, especially when the variable holds a lot of data. This will be more apparent when your code is running on resource-constrained machines.
In Python, however, this is never a problem. Youβll see why in the next section.
Returning Multiple Values
One of the most common applications of passing by reference is to create a function that alters the value of the reference parameters while returning a distinct value. You can modify your pass-by-reference C# example to illustrate this technique:
using System;
class Program
{
static void Main(string[] args)
{
int counter = 0;
// Passing by reference.
// The value of counter in Main is changed.
Console.WriteLine(greet("Alice", ref counter));
Console.WriteLine("Counter is {0}", counter);
Console.WriteLine(greet("Bob", ref counter));
Console.WriteLine("Counter is {0}", counter);
// Output:
// Hi, Alice!
// Counter is 1
// Hi, Bob!
// Counter is 2
}
static string greet(string name, ref int counter)
{
string greeting = "Hi, " + name + "!";
counter++;
return greeting;
}
}
In the example above, greet() returns a greeting string and also modifies the value of counter. Now try to reproduce this as closely as possible in Python:
>>> def main():
... counter = 0
... print(greet("Alice", counter))
... print(f"Counter is {counter}")
... print(greet("Bob", counter))
... print(f"Counter is {counter}")
...
>>> def greet(name, counter):
... counter += 1
... return f"Hi, {name}!"
...
>>> main()
Hi, Alice!
Counter is 0
Hi, Bob!
Counter is 0
counter isnβt incremented in the above example because, as youβve previously learned, Python has no way of passing values by reference. So how can you achieve the same outcome as you did with C#?
In essence, reference parameters in C# allow the function not only to return a value but also to operate on additional parameters. This is equivalent to returning multiple values!
Luckily, Python already supports returning multiple values. Strictly speaking, a Python function that returns multiple values actually returns a tuple containing each value:
>>> def multiple_return():
... return 1, 2
...
>>> t = multiple_return()
>>> t # A tuple
(1, 2)
>>> # You can unpack the tuple into two variables:
>>> x, y = multiple_return()
>>> x
1
>>> y
2
As you can see, to return multiple values, you can simply use the return keyword followed by comma-separated values or variables.
Armed with this technique, you can change the return statement in greet() from your previous Python code to return both a greeting and a counter:
>>> def main():
... counter = 0
... print(greet("Alice", counter))
... print(f"Counter is {counter}")
... print(greet("Bob", counter))
... print(f"Counter is {counter}")
...
>>> def greet(name, counter):
... return f"Hi, {name}!", counter + 1
...
>>> main()
('Hi, Alice!', 1)
Counter is 0
('Hi, Bob!', 1)
Counter is 0
That still doesnβt look right. Although greet() now returns multiple values, theyβre being printed as a tuple, which isnβt your intention. Furthermore, the original counter variable remains at 0.
To clean up your output and get the desired results, youβll have to reassign your counter variable with each call to greet():
>>> def main():
... counter = 0
... greeting, counter = greet("Alice", counter)
... print(f"{greeting}\nCounter is {counter}")
... greeting, counter = greet("Bob", counter)
... print(f"{greeting}\nCounter is {counter}")
...
>>> def greet(name, counter):
... return f"Hi, {name}!", counter + 1
...
>>> main()
Hi, Alice!
Counter is 1
Hi, Bob!
Counter is 2
Now, after reassigning each variable with a call to greet(), you can see the desired results!
Assigning return values to variables is the best way to achieve the same results as passing by reference in Python. Youβll learn why, along with some additional methods, in the section on best practices.
Creating Conditional Multiple-Return Functions
This is a specific use case of returning multiple values in which the function can be used in a conditional statement and has additional side effects like modifying an external variable that was passed in as an argument.
Consider the standard Int32.TryParse function in C#, which returns a Boolean and operates on a reference to an integer argument at the same time:
public static bool TryParse (string s, out int result);
This function attempts to convert a string into a 32-bit signed integer using the out keyword. There are two possible outcomes:
- If parsing succeeds, then the output parameter will be set to the resulting integer, and the function will return
true. - If parsing fails, then the output parameter will be set to
0, and the function will returnfalse.
You can see this in practice in the following example, which attempts to convert a number of different strings:
using System;
// Source:
// https://docs.microsoft.com/en-us/dotnet/api/system.int32.tryparse?view=netcore-3.1#System_Int32_TryParse_System_String_System_Int32__
public class Example {
public static void Main() {
String[] values = { null, "160519", "9432.0", "16,667",
" -322 ", "+4302", "(100);", "01FA" };
foreach (var value in values) {
int number;
if (Int32.TryParse(value, out number)) {
Console.WriteLine("Converted '{0}' to {1}.", value, number);
}
else {
Console.WriteLine("Attempted conversion of '{0}' failed.",
value ?? "<null>");
}
}
}
}
The above code, which attempts to convert differently formatted strings into integers via TryParse(), outputs the following:
Attempted conversion of '<null>' failed.
Converted '160519' to 160519.
Attempted conversion of '9432.0' failed.
Attempted conversion of '16,667' failed.
Converted ' -322 ' to -322.
Converted '+4302' to 4302.
Attempted conversion of '(100);' failed.
Attempted conversion of '01FA' failed.
To implement a similar function in Python, you could use multiple return values as youβve seen previously:
def tryparse(string, base=10):
try:
return True, int(string, base=base)
except ValueError:
return False, None
This tryparse() returns two values. The first value indicates whether the conversion was successful, and the second holds the result (or None, in case of failure).
However, using this function is a little clunky because you need to unpack the return values with every call. This means you canβt use the function within an if statement:
>>> success, result = tryparse("123")
>>> success
True
>>> result
123
>>> # We can make the check work
>>> # by accessing the first element of the returned tuple,
>>> # but there's no way to reassign the second element to `result`:
>>> if tryparse("456")[0]:
... print(result)
...
123
Even though it generally works by returning multiple values, tryparse() canβt be used in a condition check. That means you have some more work to do.
You can take advantage of Pythonβs flexibility and simplify the function to return a single value of different types depending on whether the conversion succeeds:
def tryparse(string, base=10):
try:
return int(string, base=base)
except ValueError:
return None
With the ability for Python functions to return different data types, you can now use this function within a conditional statement. But how? Wouldnβt you have to call the function first, assigning its return value, and then check the value itself?
By taking advantage of Pythonβs flexibility in object types, as well as the new assignment expressions in Python 3.8, you can call this simplified function within a conditional if statement and get the return value if the check passes:
>>> if (n := tryparse("123")) is not None:
... print(n)
...
123
>>> if (n := tryparse("abc")) is None:
... print(n)
...
None
>>> # You can even do arithmetic!
>>> 10 * tryparse("10")
100
>>> # All the functionality of int() is available:
>>> 10 * tryparse("0a", base=16)
100
>>> # You can also embed the check within the arithmetic expression!
>>> 10 * (n if (n := tryparse("123")) is not None else 1)
1230
>>> 10 * (n if (n := tryparse("abc")) is not None else 1)
10
Wow! This Python version of tryparse() is even more powerful than the C# version, allowing you to use it within conditional statements and in arithmetic expressions.
With a little ingenuity, youβve replicated a specific and useful pass-by-reference pattern without actually passing arguments by reference. In fact, you are yet again assigning return values when using the assignment expression operator(:=) and using the return value directly in Python expressions.
So far, youβve learned what passing by reference means, how it differs from passing by value, and how Pythonβs approach is different from both. Now youβre ready to take a closer look at how Python handles function arguments!
Passing Arguments in Python
Python passes arguments by assignment. That is, when you call a Python function, each function argument becomes a variable to which the passed value is assigned.
Therefore, you can learn important details about how Python handles function arguments by understanding how the assignment mechanism itself works, even outside functions.
Understanding Assignment in Python
Pythonβs language reference for assignment statements provides the following details:
- If the assignment target is an identifier, or variable name, then this name is bound to the object. For example, in
x = 2,xis the name and2is the object. - If the name is already bound to a separate object, then itβs re-bound to the new object. For example, if
xis already2and you issuex = 3, then the variable namexis re-bound to3.
All Python objects are implemented in a particular structure. One of the properties of this structure is a counter that keeps track of how many names have been bound to this object.
Note: This counter is called a reference counter because it keeps track of how many references, or names, point to the same object. Do not confuse reference counter with the concept of passing by reference, as the two are unrelated.
The Python documentation provides additional details on reference counts.
Letβs stick to the x = 2 example and examine what happens when you assign a value to a new variable:
- If an object representing the value
2already exists, then itβs retrieved. Otherwise, itβs created. - The reference counter of this object is incremented.
- An entry is added in the current namespace to bind the identifier
xto the object representing2. This entry is in fact a key-value pair stored in a dictionary! A representation of that dictionary is returned bylocals()orglobals().
Now hereβs what happens if you reassign x to a different value:
- The reference counter of the object representing
2is decremented. - The reference counter of the object that represents the new value is incremented.
- The dictionary for the current namespace is updated to relate
xto the object representing the new value.
Python allows you to obtain the reference counts for arbitrary values with the function sys.getrefcount(). You can use it to illustrate how assignment increases and decreases these reference counters. Note that the interactive interpreter employs behavior that will yield different results, so you should run the following code from a file:
from sys import getrefcount
print("--- Before assignment ---")
print(f"References to value_1: {getrefcount('value_1')}")
print(f"References to value_2: {getrefcount('value_2')}")
x = "value_1"
print("--- After assignment ---")
print(f"References to value_1: {getrefcount('value_1')}")
print(f"References to value_2: {getrefcount('value_2')}")
x = "value_2"
print("--- After reassignment ---")
print(f"References to value_1: {getrefcount('value_1')}")
print(f"References to value_2: {getrefcount('value_2')}")
This script will show the reference counts for each value prior to assignment, after assignment, and after reassignment:
--- Before assignment ---
References to value_1: 3
References to value_2: 3
--- After assignment ---
References to value_1: 4
References to value_2: 3
--- After reassignment ---
References to value_1: 3
References to value_2: 4
These results illustrate the relationship between identifiers (variable names) and Python objects that represent distinct values. When you assign multiple variables to the same value, Python increments the reference counter for the existing object and updates the current namespace rather than creating duplicate objects in memory.
In the next section, youβll build upon your current understanding of assignment operations by exploring how Python handles function arguments.
Exploring Function Arguments
Function arguments in Python are local variables. What does that mean? Local is one of Pythonβs scopes. These scopes are represented by the namespace dictionaries mentioned in the previous section. You can use locals() and globals() to retrieve the local and global namespace dictionaries, respectively.
Upon execution, each function has its own local namespace:
>>> def show_locals():
... my_local = True
... print(locals())
...
>>> show_locals()
{'my_local': True}
Using locals(), you can demonstrate that function arguments become regular variables in the functionβs local namespace. Letβs add an argument, my_arg, to the function:
>>> def show_locals(my_arg):
... my_local = True
... print(locals())
...
>>> show_locals("arg_value")
{'my_arg': 'arg_value', 'my_local': True}
You can also use sys.getrefcount() to show how function arguments increment the reference counter for an object:
>>> from sys import getrefcount
>>> def show_refcount(my_arg):
... return getrefcount(my_arg)
...
>>> getrefcount("my_value")
3
>>> show_refcount("my_value")
5
The above script outputs reference counts for "my_value" first outside, then inside show_refcount(), showing a reference count increase of not one, but two!
Thatβs because, in addition to show_refcount() itself, the call to sys.getrefcount() inside show_refcount() also receives my_arg as an argument. This places my_arg in the local namespace for sys.getrefcount(), adding an extra reference to "my_value".
By examining namespaces and reference counts inside functions, you can see that function arguments work exactly like assignments: Python creates bindings in the functionβs local namespace between identifiers and Python objects that represent argument values. Each of these bindings increments the objectβs reference counter.
Now you can see how Python passes arguments by assignment!
Replicating Pass by Reference With Python
Having examined namespaces in the previous section, you may be asking why global hasnβt been mentioned as one way to modify variables as if they were passed by reference:
>>> def square():
... # Not recommended!
... global n
... n *= n
...
>>> n = 4
>>> square()
>>> n
16
Using the global statement generally takes away from the clarity of your code. It can create a number of issues, including the following:
- Free variables, seemingly unrelated to anything
- Functions without explicit arguments for said variables
- Functions that canβt be used generically with other variables or arguments since they rely on a single global variable
- Lack of thread safety when using global variables
Contrast the previous example with the following, which explicitly returns a value:
>>> def square(n):
... return n * n
...
>>> square(4)
16
Much better! You avoid all potential issues with global variables, and by requiring an argument, you make your function clearer.
Despite being neither a pass-by-reference language nor a pass-by-value language, Python suffers no shortcomings in that regard. Its flexibility more than meets the challenge.
Best Practice: Return and Reassign
Youβve already touched on returning values from the function and reassigning them to a variable. For functions that operate on a single value, returning the value is much clearer than using a reference. Furthermore, since Python already uses pointers behind the scenes, there would be no additional performance benefits even if it were able to pass arguments by reference.
Aim to write single-purpose functions that return one value, then (re)assign that value to variables, as in the following example:
def square(n):
# Accept an argument, return a value.
return n * n
x = 4
...
# Later, reassign the return value:
x = square(x)
Returning and assigning values also makes your intention explicit and your code easier to understand and test.
For functions that operate on multiple values, youβve already seen that Python is capable of returning a tuple of values. You even surpassed the elegance of Int32.TryParse() in C# thanks to Pythonβs flexibility!
If you need to operate on multiple values, then you can write single-purpose functions that return multiple values, then (re)assign those values to variables. Hereβs an example:
def greet(name, counter):
# Return multiple values
return f"Hi, {name}!", counter + 1
counter = 0
...
# Later, reassign each return value by unpacking.
greeting, counter = greet("Alice", counter)
When calling a function that returns multiple values, you can assign multiple variables at the same time.
Best Practice: Use Object Attributes
Object attributes have their own place in Pythonβs assignment strategy. Pythonβs language reference for assignment statements states that if the target is an objectβs attribute that supports assignment, then the object will be asked to perform the assignment on that attribute. If you pass the object as an argument to a function, then its attributes can be modified in place.
Write functions that accept objects with attributes, then operate directly on those attributes, as in the following example:
>>> # For the purpose of this example, let's use SimpleNamespace.
>>> from types import SimpleNamespace
>>> # SimpleNamespace allows us to set arbitrary attributes.
>>> # It is an explicit, handy replacement for "class X: pass".
>>> ns = SimpleNamespace()
>>> # Define a function to operate on an object's attribute.
>>> def square(instance):
... instance.n *= instance.n
...
>>> ns.n = 4
>>> square(ns)
>>> ns.n
16
Note that square() needs to be written to operate directly on an attribute, which will be modified without the need to reassign a return value.
Itβs worth repeating that you should make sure the attribute supports assignment! Hereβs the same example with namedtuple, whose attributes are read-only:
>>> from collections import namedtuple
>>> NS = namedtuple("NS", "n")
>>> def square(instance):
... instance.n *= instance.n
...
>>> ns = NS(4)
>>> ns.n
4
>>> square(ns)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in square
AttributeError: can't set attribute
Attempts to modify attributes that donβt allow modification result in an AttributeError.
Additionally, you should be mindful of class attributes. They will remain unchanged, and an instance attribute will be created and modified:
>>> class NS:
... n = 4
...
>>> ns = NS()
>>> def square(instance):
... instance.n *= instance.n
...
>>> ns.n
4
>>> square(ns)
>>> # Instance attribute is modified.
>>> ns.n
16
>>> # Class attribute remains unchanged.
>>> NS.n
4
Since class attributes remain unchanged when modified through a class instance, youβll need to remember to reference the instance attribute.
Best Practice: Use Dictionaries and Lists
Dictionaries in Python are a different object type than all other built-in types. Theyβre referred to as mapping types. Pythonβs documentation on mapping types provides some insight into the term:
A mapping object maps hashable values to arbitrary objects. Mappings are mutable objects. There is currently only one standard mapping type, the dictionary. (Source)
This tutorial doesnβt cover how to implement a custom mapping type, but you can replicate pass by reference using the humble dictionary. Hereβs an example using a function that operates directly on dictionary elements:
>>> # Dictionaries are mapping types.
>>> mt = {"n": 4}
>>> # Define a function to operate on a key:
>>> def square(num_dict):
... num_dict["n"] *= num_dict["n"]
...
>>> square(mt)
>>> mt
{'n': 16}
Since youβre reassigning a value to a dictionary key, operating on dictionary elements is still a form of assignment. With dictionaries, you get the added practicality of accessing the modified value through the same dictionary object.
While lists arenβt mapping types, you can use them in a similar way to dictionaries because of two important characteristics: subscriptability and mutability. These characteristics are worthy of a little more explanation, but letβs first take a look at best practices for mimicking pass by reference using Python lists.
To replicate pass by reference using lists, write a function that operates directly on list elements:
>>> # Lists are both subscriptable and mutable.
>>> sm = [4]
>>> # Define a function to operate on an index:
>>> def square(num_list):
... num_list[0] *= num_list[0]
...
>>> square(sm)
>>> sm
[16]
Since youβre reassigning a value to an element within the list, operating on list elements is still a form of assignment. Similar to dictionaries, lists allow you to access the modified value through the same list object.
Now letβs explore subscriptability. An object is subscriptable when a subset of its structure can be accessed by index positions:
>>> subscriptable = [0, 1, 2] # A list
>>> subscriptable[0]
0
>>> subscriptable = (0, 1, 2) # A tuple
>>> subscriptable[0]
0
>>> subscriptable = "012" # A string
>>> subscriptable[0]
'0'
>>> not_subscriptable = {0, 1, 2} # A set
>>> not_subscriptable[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'set' object is not subscriptable
Lists, tuples, and strings are subscriptable, but sets are not. Attempting to access an element of an object that isnβt subscriptable will raise a TypeError.
Mutability is a broader topic requiring additional exploration and documentation reference. To keep things short, an object is mutable if its structure can be changed in place rather than requiring reassignment:
>>> mutable = [0, 1, 2] # A list
>>> mutable[0] = "x"
>>> mutable
['x', 1, 2]
>>> not_mutable = (0, 1, 2) # A tuple
>>> not_mutable[0] = "x"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> not_mutable = "012" # A string
>>> not_mutable[0] = "x"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> mutable = {0, 1, 2} # A set
>>> mutable.remove(0)
>>> mutable.add("x")
>>> mutable
{1, 2, 'x'}
Lists and sets are mutable, as are dictionaries and other mapping types. Strings and tuples are not mutable. Attempting to modify an element of an immutable object will raise a TypeError.
Conclusion
Python works differently from languages that support passing arguments by reference or by value. Function arguments become local variables assigned to each value that was passed to the function. But this doesnβt prevent you from achieving the same results youβd expect when passing arguments by reference in other languages.
In this tutorial, you learned:
- How Python handles assigning values to variables
- How function arguments are passed by assignment in Python
- Why returning values is a best practice for replicating pass by reference
- How to use attributes, dictionaries, and lists as alternative best practices
You also learned some additional best practices for replicating pass-by-reference constructs in Python. You can use this knowledge to implement patterns that have traditionally required support for passing by reference.
To continue your Python journey, I encourage you to dive deeper into some of the related topics that youβve encountered here, such as mutability, assignment expressions, and Python namespaces and scope.
Stay curious, and see you next time!
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Pass by Reference in Python: Best Practices



