Passing objects to functions

In Python, everything is an object, including functions themselves. When passing objects to functions, Python uses a mechanism known as “pass-by-object-reference.” This means that functions receive references to the objects passed in, not the actual objects themselves.

How Passing Objects to Functions Works

Immutable Objects

Immutable objects include integers, floats, strings, and tuples. When you pass an immutable object to a function, any changes to that object within the function do not affect the original object outside the function. This is because a new object is created if a modification is made.

Example:

def modify_value(x):
    x = 10
    print(f"Inside function: x = {x}")

a = 5
modify_value(a)
print(f"Outside function: a = {a}")

Output:

Inside function: x = 10
Outside function: a = 5

Mutable Objects

Mutable objects include lists, dictionaries, sets, and most user-defined objects. When you pass a mutable object to a function, changes to the object within the function do affect the original object outside the function. This is because the function operates on the original object reference.

Example:

def modify_list(lst):
    lst.append(4)
    print(f"Inside function: lst = {lst}")

my_list = [1, 2, 3]
modify_list(my_list)
print(f"Outside function: my_list = {my_list}")

Output:

Inside function: lst = [1, 2, 3, 4]
Outside function: my_list = [1, 2, 3, 4]

Passing User-Defined Objects

User-defined objects can also be passed to functions. If the object is mutable, any changes made to its attributes within the function will reflect outside the function.

Example:

class MyClass:
    def __init__(self, value):
        self.value = value

def modify_object(obj):
    obj.value += 10
    print(f"Inside function: obj.value = {obj.value}")

my_obj = MyClass(5)
modify_object(my_obj)
print(f"Outside function: my_obj.value = {my_obj.value}")

Output:

Inside function: obj.value = 15
Outside function: my_obj.value = 15

Passing Functions as Arguments

Functions in Python are first-class objects, meaning they can be passed as arguments to other functions.

Example:

def apply_function(func, value):
    return func(value)

def square(x):
    return x * x

result = apply_function(square, 5)
print(result)  # Output: 25

Passing Arbitrary Arguments

Python allows passing an arbitrary number of arguments to a function using *args for non-keyword arguments and **kwargs for keyword arguments.

Example:

def print_args(*args):
    for arg in args:
        print(arg)

print_args(1, 2, 3)  # Output: 1 2 3

def print_kwargs(**kwargs):
    for key, value in kwargs.items():
        print(f"{key} = {value}")

print_kwargs(a=1, b=2, c=3)  # Output: a = 1, b = 2, c = 3

Copying Objects

If you want to avoid modifications to mutable objects passed to functions, you can pass a copy of the object instead.

Example with a list:

import copy

def modify_list(lst):
    lst.append(4)
    print(f"Inside function: lst = {lst}")

my_list = [1, 2, 3]
modify_list(my_list[:])  # Using slicing to create a shallow copy
print(f"Outside function: my_list = {my_list}")

Output:

Inside function: lst = [1, 2, 3, 4]
Outside function: my_list = [1, 2, 3]

Using the copy module for a deep copy:

import copy

def modify_list(lst):
    lst.append(4)
    print(f"Inside function: lst = {lst}")

my_list = [1, 2, 3]
modify_list(copy.deepcopy(my_list))
print(f"Outside function: my_list = {my_list}")

Output:

Inside function: lst = [1, 2, 3, 4]
Outside function: my_list = [1, 2, 3]

Understanding how objects are passed to functions in Python helps in writing clear and predictable code, especially when dealing with mutable and immutable objects.