NeuroAgent

Python Pass by Reference: Complete Guide

Learn how Python's pass-by-object-reference works with mutable vs immutable objects. Complete guide with examples to modify variables in functions.

Question

How do I pass a variable by reference in Python?

I wrote this class for testing:

python
class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self.variable)
        print(self.variable)

    def change(self, var):
        var = 'Changed'

When I tried creating an instance, the output was Original. So it seems like parameters in Python are passed by value. Is that correct? How can I modify the code to get the effect of pass-by-reference, so that the output is Changed?

Sometimes people are surprised that code like x = 1, where x is a parameter name, doesn’t impact on the caller’s argument, but code like x[0] = 1 does. This happens because item assignment and slice assignment are ways to mutate an existing object, rather than reassign a variable, despite the = syntax. See Why can a function modify some arguments as perceived by the caller, but not others? for details.

See also What’s the difference between passing by reference vs. passing by value? for important, language-agnostic terminology discussion.

NeuroAgent

Python doesn’t support traditional pass-by-reference like languages such as C++ or Java. Instead, Python uses a pass-by-object-reference mechanism, where you pass references to objects rather than the objects themselves. Your code doesn’t work because strings are immutable in Python - when you assign var = 'Changed', you’re creating a new string object and binding the parameter var to it, rather than modifying the original object.


Contents


Understanding Python’s Parameter Passing Mechanism

Python uses a pass-by-assignment approach, which combines elements of both pass-by-value and pass-by-reference behaviors. When you pass an argument to a function, Python creates a binding between the function parameter name and the object that was passed.

As Real Python explains: “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.”

The key insight is that Python always passes object references. Whether this behaves like pass-by-value or pass-by-reference depends on whether the object is mutable or immutable:

  • Mutable objects (lists, dictionaries, sets): Can be modified in place
  • Immutable objects (strings, numbers, tuples): Cannot be changed after creation

Why Your Code Doesn’t Work: Immutable Objects

Your code fails because self.variable is a string, which is immutable in Python. When you call change(self.variable), Python creates a new binding where the parameter var refers to the same string object as self.variable. However, when you execute var = 'Changed', you’re not modifying the original string - you’re creating a new string and rebinding var to point to it.

The Medium article on Python parameter passing explains this perfectly: “With a = 3, we have the variable a referring to the object with value 3.”

Here’s what happens step by step:

python
# In your __init__ method:
self.variable = 'Original'  # Creates string object 'Original'
self.change(self.variable)  # Passes reference to 'Original' string

# In change method:
def change(self, var):      # var now refers to same object as self.variable
    var = 'Changed'         # Creates new string 'Changed', var now points to it
    # Original 'Original' string remains unchanged

How to Achieve Pass-by-Reference Behavior

To achieve the effect you want, you need to modify the instance attribute directly rather than trying to pass the string value. Here are several approaches:

Method 1: Pass the instance itself

python
class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self)
        print(self.variable)

    def change(self, instance):
        instance.variable = 'Changed'

Method 2: Use a mutable container

python
class PassByReference:
    def __init__(self):
        self.variable = ['Original']  # Use list instead of string
        self.change(self.variable)
        print(self.variable[0])

    def change(self, var):
        var[0] = 'Changed'  # Modify the list contents

Method 3: Return the new value and assign it

python
class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.variable = self.change(self.variable)
        print(self.variable)

    def change(self, var):
        return 'Changed'

Mutable vs Immutable Objects in Function Calls

The behavior you’re experiencing stems from the fundamental difference between mutable and immutable objects in Python:

Mutable Objects (Can be modified)

  • Lists, dictionaries, sets, custom classes
  • When passed to functions, their contents can be modified
  • The original object in the caller’s scope reflects these changes
python
def modify_list(lst):
    lst.append('new element')  # Modifies the original list

my_list = [1, 2, 3]
modify_list(my_list)
print(my_list)  # Output: [1, 2, 3, 'new element']

Immutable Objects (Cannot be modified)

  • Strings, numbers, tuples
  • When passed to functions, any assignment creates new objects
  • The original object in the caller’s scope remains unchanged
python
def modify_string(s):
    s = s + ' modified'  # Creates new string, doesn't affect original

my_string = 'hello'
modify_string(my_string)
print(my_string)  # Output: 'hello' (unchanged)

As GeeksforGeeks states: “Whether the original data changes depends on whether the object is mutable (can be changed in place) or immutable (cannot be changed once created).”

Practical Examples and Best Practices

Example 1: Modifying a Dictionary

python
def add_to_dict(d, key, value):
    d[key] = value  # This modifies the original dictionary

my_dict = {'name': 'Alice'}
add_to_dict(my_dict, 'age', 25)
print(my_dict)  # Output: {'name': 'Alice', 'age': 25}

Example 2: Modifying a List Contents

python
def modify_list_contents(lst):
    lst[0] = 'changed'  # This works - modifies existing list
    # lst = ['new']  # This doesn't work - creates new list reference

my_list = ['original', 2, 3]
modify_list_contents(my_list)
print(my_list)  # Output: ['changed', 2, 3]

Example 3: The “Reassignment” Trap

python
def reassign_parameter(param):
    param = 'new value'  # This only affects local parameter

my_var = 'original'
reassign_parameter(my_var)
print(my_var)  # Output: 'original' - unchanged

Common Pitfalls and Solutions

Pitfall 1: Confusing Assignment with Mutation

python
def problematic_function(x):
    x = 10  # Assignment - creates new object
    x[0] = 10  # Mutation - modifies existing object (only works for mutable)

# For immutable objects, assignment doesn't affect caller
# For mutable objects, mutation affects caller but assignment doesn't

Pitfall 2: Mutable Default Arguments

python
def bad_function(items=[]):  # Dangerous - same list object reused
    items.append('item')
    return items

print(bad_function())  # ['item']
print(bad_function())  # ['item', 'item'] - problem!

# Solution: Use None as default
def good_function(items=None):
    if items is None:
        items = []
    items.append('item')
    return items

Solution: Use Clear Documentation

python
def process_data(data):
    """
    Modifies the passed data object in place.
    For immutable data, returns modified copy.
    """
    if isinstance(data, (str, int, float, tuple)):
        # Handle immutable objects by returning new object
        return f"processed_{data}"
    else:
        # Handle mutable objects by modifying in place
        data.processed = True
        return data  # Return for consistency

Conclusion

To summarize the key points about Python’s parameter passing mechanism:

  1. Python uses pass-by-object-reference, not traditional pass-by-value or pass-by-reference
  2. Immutable objects (strings, numbers, tuples) cannot be modified - any assignment creates new objects
  3. Mutable objects (lists, dictionaries, sets) can be modified in place, affecting the original object
  4. To achieve the effect you want in your code, either pass the instance itself, use mutable containers, or return new values
  5. The key distinction is between reassignment (which always creates new bindings) and mutation (which can modify existing objects for mutable types)

For your specific case, the best solution would be to pass the instance to the change method rather than the instance attribute, as shown in Method 1 above. This gives you direct access to modify the object’s state rather than trying to modify immutable string values.


Sources

  1. Pass by reference vs value in Python - GeeksforGeeks
  2. Understanding Python’s Pass by Assignment - Medium
  3. Pass-by-value, reference, and assignment - mathspp
  4. Pass by Reference in Python: Background and Best Practices – Real Python
  5. Tricky Python II: Parameter Passing for Mutable & Immutable Objects - Medium
  6. Python: Passing mutable object to method - Stack Overflow
  7. Why can a function modify some arguments as perceived by the caller, but not others? - Stack Overflow