Qingquan-Li / blog

My Blog
https://Qingquan-Li.github.io/blog/
132 stars 16 forks source link

Python Function arguments and mutability #184

Open Qingquan-Li opened 2 years ago

Qingquan-Li commented 2 years ago

1. Function arguments

Arguments to functions are passed by object reference, a concept known in Python as pass-by-assignment. When a function is called, new local variables are created in the function's local namespace by binding the names in the parameter list to the passed arguments.

def birthday(age):
    '''Celebrate birthday!'''
    age = age + 1

timmy_age = 7

birthday(timmy_age)
print('Timmy is', timmy_age)  # Output: Timmy is 7

# 1. timmy_age and age reference the same object.
# 2. Assigning the parameter age with a new value doesn't change timmy_age.
# 3. Since timmy_age has not changed, "Timmy is 7" is displayed.

1.1 Keyword Arguments and Positional Arguments

def student_info(stu_id, name, pronouns, age, school="Hogwarts"):
    print(f'{name}, {stu_id}, {pronouns}, Age: {age}, {school}')

student_info(123, name='Bob', pronouns='he/him/his', age=18)
# Bob, 123, he/him/his, Age: 18, Hogwarts


2. Mutability of the argument object

The semantics of passing object references as arguments is important because modifying an argument that is referenced elsewhere in the program may cause side effects outside of the function scope. When a function modifies a parameter, whether or not that modification is seen outside the scope of the function depends on the mutability of the argument object.


2.1 How to pass a mutable object to a function

The following program illustrates how the modification of a list argument's elements inside a function persists outside of the function call.

def modify(num_list):
    num_list[1] = 99

my_list = [10, 20, 30]
modify(my_list)
print(my_list)  # my_list still contains 99!

Sometimes a programmer needs to pass a mutable object to a function but wants to make sure that the function does not modify the object at all. One method to avoid unwanted changes is to pass a copy of the object as the argument instead, like in the statement my_func(num_list[:]):

def modify(num_list):
    num_list[1] = 99  # Modifying only the copy

my_list = [10, 20, 30]
modify(my_list[:])  # Pass a copy of the list 

print(my_list)  # my_list does not contain 99!


2.2 How to provide a mutable object as a default parameter

A common error is to provide a mutable object, like a list, as a default parameter. Such a definition can be problematic because the default argument object is created only once, at the time the function is defined (when the script is loaded), and not every time the function is called. Modification of the default parameter object will persist across function calls, which is likely not what a programmer intended. The below program demonstrates the problem with mutable default objects and illustrates a solution that creates a new empty list each time the function is called:

""" Default object modification
This program shows a function append_to_list() that has an empty list as default value of my_list.
A programmer might expect that each time the function is called without specifying my_list, a new
empty list will be created and the result of the function will be [value].
However, the default object persists across function calls. """

def append_to_list(value, my_list=[]):
    my_list.append(value)
    return my_list

numbers = append_to_list(50)  # default list appended with 50
print(numbers)  # [50]
numbers = append_to_list(100)  # default list appended with 100
print(numbers)  # [50, 100]
""" Solution: Make a new list
The solution replaces the default list with None, checking for that value, and then creating a new
empty list in the local scope if necessary. """

# Use default parameter value of None
def append_to_list(value, my_list=None):
    if my_list == None:
        # Create a new list if a list was not provided
        my_list = []

    my_list.append(value)
    return my_list

numbers = append_to_list(50)  # default list appended with 50
print(numbers)  # [50]
numbers = append_to_list(100)  # default list appended with 100
print(numbers)  # [100]


Reference: zyBooks > Principles in Information Technology and Computation > 12.8 Function arguments & 12.9 Keyword arguments and default parameter values